|
import pydicom |
|
import datetime |
|
import numpy as np |
|
import scipy |
|
import nibabel as nib |
|
|
|
class PatientInfo: |
|
|
|
def __init__(self): |
|
self.PatientID = '' |
|
self.PatientName = '' |
|
self.PatientBirthDate = '' |
|
self.PatientSex = '' |
|
|
|
class StudyInfo: |
|
|
|
def __init__(self): |
|
self.StudyInstanceUID = '' |
|
self.StudyID = '' |
|
self.StudyDate = '' |
|
self.StudyTime = '' |
|
|
|
class RTdose: |
|
|
|
def __init__(self): |
|
self.SeriesInstanceUID = "" |
|
self.SOPInstanceUID = "" |
|
self.PatientInfo = PatientInfo() |
|
self.StudyInfo = StudyInfo() |
|
self.CT_SeriesInstanceUID = "" |
|
self.Plan_SOPInstanceUID = "" |
|
self.FrameOfReferenceUID = "" |
|
self.ImgName = "" |
|
self.beam_number = "" |
|
self.DcmFile = "" |
|
self.isLoaded = 0 |
|
|
|
def print_dose_info(self, prefix=""): |
|
print(prefix + "Dose: " + self.SOPInstanceUID) |
|
print(prefix + " " + self.DcmFile) |
|
|
|
|
|
|
|
def import_Dicom_dose(self, CT): |
|
if(self.isLoaded == 1): |
|
print("Warning: Dose image " + self.SOPInstanceUID + " is already loaded") |
|
return |
|
|
|
dcm = pydicom.dcmread(self.DcmFile) |
|
|
|
self.CT_SeriesInstanceUID = CT.SeriesInstanceUID |
|
|
|
|
|
|
|
if(dcm.BitsStored == 16 and dcm.PixelRepresentation == 0): |
|
dt = np.dtype('uint16') |
|
elif(dcm.BitsStored == 16 and dcm.PixelRepresentation == 1): |
|
dt = np.dtype('int16') |
|
elif(dcm.BitsStored == 32 and dcm.PixelRepresentation == 0): |
|
dt = np.dtype('uint32') |
|
elif(dcm.BitsStored == 32 and dcm.PixelRepresentation == 1): |
|
dt = np.dtype('int32') |
|
else: |
|
print("Error: Unknown data type for " + self.DcmFile) |
|
return |
|
|
|
if(dcm.HighBit == dcm.BitsStored-1): |
|
dt = dt.newbyteorder('L') |
|
else: |
|
dt = dt.newbyteorder('B') |
|
|
|
dose_image = np.frombuffer(dcm.PixelData, dtype=dt) |
|
dose_image = dose_image.reshape((dcm.Columns, dcm.Rows, dcm.NumberOfFrames), order='F').transpose(1,0,2) |
|
dose_image = dose_image * dcm.DoseGridScaling |
|
|
|
self.Image = dose_image |
|
self.FrameOfReferenceUID = dcm.FrameOfReferenceUID |
|
self.ImagePositionPatient = dcm.ImagePositionPatient |
|
if dcm.SliceThickness is not None: |
|
self.PixelSpacing = [dcm.PixelSpacing[0], dcm.PixelSpacing[1], dcm.SliceThickness] |
|
else: |
|
self.PixelSpacing = [dcm.PixelSpacing[0], dcm.PixelSpacing[1], dcm.GridFrameOffsetVector[1]-dcm.GridFrameOffsetVector[0]] |
|
self.GridSize = [dcm.Columns, dcm.Rows, dcm.NumberOfFrames] |
|
self.NumVoxels = self.GridSize[0] * self.GridSize[1] * self.GridSize[2] |
|
|
|
if hasattr(dcm, 'GridFrameOffsetVector'): |
|
if(dcm.GridFrameOffsetVector[1] - dcm.GridFrameOffsetVector[0] < 0): |
|
self.Image = np.flip(self.Image, 2) |
|
self.ImagePositionPatient[2] = self.ImagePositionPatient[2] - self.GridSize[2]*self.PixelSpacing[2] |
|
|
|
self.resample_to_CT_grid(CT) |
|
self.isLoaded = 1 |
|
|
|
|
|
|
|
def euclidean_dist(self, v1, v2): |
|
return sum((p-q)**2 for p, q in zip(v1, v2)) ** .5 |
|
|
|
|
|
|
|
def resample_to_CT_grid(self, CT): |
|
if(self.GridSize == CT.GridSize and self.euclidean_dist(self.ImagePositionPatient, CT.ImagePositionPatient) < 0.001 and self.euclidean_dist(self.PixelSpacing, CT.PixelSpacing) < 0.001): |
|
return |
|
else: |
|
|
|
sigma = [0, 0, 0] |
|
if(CT.PixelSpacing[0] > self.PixelSpacing[0]): sigma[0] = 0.4 * (CT.PixelSpacing[0]/self.PixelSpacing[0]) |
|
if(CT.PixelSpacing[1] > self.PixelSpacing[1]): sigma[1] = 0.4 * (CT.PixelSpacing[1]/self.PixelSpacing[1]) |
|
if(CT.PixelSpacing[2] > self.PixelSpacing[2]): sigma[2] = 0.4 * (CT.PixelSpacing[2]/self.PixelSpacing[2]) |
|
if(sigma != [0, 0, 0]): |
|
print("Image is filtered before downsampling") |
|
self.Image = scipy.ndimage.gaussian_filter(self.Image, sigma) |
|
|
|
|
|
print('Resample dose image to CT grid.') |
|
|
|
x = self.ImagePositionPatient[1] + np.arange(self.GridSize[1]) * self.PixelSpacing[1] |
|
y = self.ImagePositionPatient[0] + np.arange(self.GridSize[0]) * self.PixelSpacing[0] |
|
z = self.ImagePositionPatient[2] + np.arange(self.GridSize[2]) * self.PixelSpacing[2] |
|
|
|
xi = np.array(np.meshgrid(CT.VoxelY, CT.VoxelX, CT.VoxelZ)) |
|
xi = np.rollaxis(xi, 0, 4) |
|
xi = xi.reshape((xi.size // 3, 3)) |
|
|
|
self.Image = scipy.interpolate.interpn((x,y,z), self.Image, xi, method='linear', fill_value=0, bounds_error=False) |
|
self.Image = self.Image.reshape((CT.GridSize[0], CT.GridSize[1], CT.GridSize[2])).transpose(1,0,2) |
|
|
|
self.ImagePositionPatient = CT.ImagePositionPatient |
|
self.PixelSpacing = CT.PixelSpacing |
|
self.GridSize = CT.GridSize |
|
self.NumVoxels = CT.NumVoxels |
|
|
|
|
|
def load_from_nii(self, dose_nii): |
|
|
|
|
|
img = nib.load(dose_nii) |
|
|
|
self.Image = img.get_fdata() |
|
self.GridSize = self.Image.shape |
|
self.PixelSpacing = [img.header['pixdim'][1], img.header['pixdim'][2], img.header['pixdim'][3]] |
|
self.ImagePositionPatient = [ img.affine[0][3], img.affine[1][3], img.affine[2][3]] |
|
|
|
def export_Dicom(self, refCT, OutputFile): |
|
|
|
|
|
SOPInstanceUID = pydicom.uid.generate_uid() |
|
meta = pydicom.dataset.FileMetaDataset() |
|
meta.MediaStorageSOPClassUID = '1.2.840.10008.5.1.4.1.1.481.2' |
|
meta.MediaStorageSOPInstanceUID = SOPInstanceUID |
|
|
|
|
|
meta.ImplementationClassUID = '1.2.826.0.1.3680043.1.2.100.6.40.0.76' |
|
|
|
|
|
meta.FileMetaInformationGroupLength = 200 |
|
|
|
|
|
|
|
|
|
|
|
|
|
dcm_file = pydicom.dataset.FileDataset(OutputFile, {}, file_meta=meta, preamble=b"\0" * 128) |
|
|
|
|
|
dcm_file.file_meta.TransferSyntaxUID = pydicom.uid.ExplicitVRLittleEndian |
|
print(dcm_file.file_meta.TransferSyntaxUID) |
|
dcm_file.is_little_endian = True |
|
dcm_file.is_implicit_VR = False |
|
|
|
|
|
dcm_file.PatientName = refCT.PatientInfo.PatientName |
|
dcm_file.PatientID = refCT.PatientInfo.PatientID |
|
dcm_file.PatientBirthDate = refCT.PatientInfo.PatientBirthDate |
|
dcm_file.PatientSex = refCT.PatientInfo.PatientSex |
|
|
|
|
|
dt = datetime.datetime.now() |
|
dcm_file.StudyDate = dt.strftime('%Y%m%d') |
|
dcm_file.StudyTime = dt.strftime('%H%M%S.%f') |
|
dcm_file.AccessionNumber = '1' |
|
dcm_file.ReferringPhysicianName = 'NA' |
|
dcm_file.StudyInstanceUID = refCT.StudyInfo.StudyInstanceUID |
|
dcm_file.StudyID = refCT.StudyInfo.StudyID |
|
|
|
|
|
dcm_file.Modality = 'RTDOSE' |
|
dcm_file.SeriesDescription = 'AI-predicted' + dt.strftime('%Y%m%d') + dt.strftime('%H%M%S.%f') |
|
dcm_file.OperatorsName = 'MIRO' |
|
dcm_file.SeriesInstanceUID = pydicom.uid.generate_uid() |
|
dcm_file.SeriesNumber = 1 |
|
|
|
|
|
dcm_file.FrameOfReferenceUID = refCT.FrameOfReferenceUID |
|
dcm_file.PositionReferenceIndicator = '' |
|
|
|
|
|
dcm_file.Manufacturer = 'echarp' |
|
|
|
|
|
|
|
|
|
dcm_file.ContentDate = dt.strftime('%Y%m%d') |
|
dcm_file.ContentTime = dt.strftime('%H%M%S.%f') |
|
dcm_file.InstanceNumber = 1 |
|
dcm_file.PatientOrientation = '' |
|
|
|
|
|
dcm_file.SliceThickness = self.PixelSpacing[2] |
|
dcm_file.ImagePositionPatient = self.ImagePositionPatient |
|
dcm_file.ImageOrientationPatient = [1, 0, 0, 0, 1, 0] |
|
dcm_file.PixelSpacing = self.PixelSpacing[0:2] |
|
|
|
|
|
dcm_file.SamplesPerPixel = 1 |
|
dcm_file.PhotometricInterpretation = 'MONOCHROME2' |
|
dcm_file.Rows = self.GridSize[1] |
|
dcm_file.Columns = self.GridSize[0] |
|
dcm_file.BitsAllocated = 16 |
|
dcm_file.BitsStored = 16 |
|
dcm_file.HighBit = 15 |
|
dcm_file.BitDepth = 16 |
|
dcm_file.PixelRepresentation = 0 |
|
|
|
|
|
|
|
dcm_file.NumberOfFrames = self.GridSize[2] |
|
dcm_file.FrameIncrementPointer = pydicom.tag.Tag((0x3004, 0x000c)) |
|
|
|
|
|
dcm_file.DoseUnits = 'GY' |
|
dcm_file.DoseType = 'PHYSICAL' |
|
dcm_file.DoseSummationType = 'PLAN' |
|
dcm_file.GridFrameOffsetVector = list(np.arange(0, self.GridSize[2]*self.PixelSpacing[2], self.PixelSpacing[2])) |
|
dcm_file.DoseGridScaling = self.Image.max()/(2**dcm_file.BitDepth - 1) |
|
|
|
dcm_file.PixelData = (self.Image/dcm_file.DoseGridScaling).astype(np.uint16).transpose(2,0,1).tostring() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
dcm_file.SpecificCharacterSet = 'ISO_IR 100' |
|
dcm_file.InstanceCreationDate = dt.strftime('%Y%m%d') |
|
dcm_file.InstanceCreationTime = dt.strftime('%H%M%S.%f') |
|
dcm_file.SOPClassUID = meta.MediaStorageSOPClassUID |
|
dcm_file.SOPInstanceUID = SOPInstanceUID |
|
|
|
|
|
print("Export dicom RTDOSE: " + OutputFile) |
|
dcm_file.save_as(OutputFile) |
|
|
|
|
|
|
|
|
|
|