a b/data_conversion/dicom_to_nifti.py
1
'''
2
Copyright (c) Microsoft Corporation. All rights reserved.
3
Licensed under the MIT License.
4
'''
5
6
'''
7
This code does the following:
8
(1) converts PET DICOM images in units Bq/ml to decay-corrected SUV and saved as 3D NIFTI files
9
(2) converts CT DICOM images to NIFTI
10
(3) converts DICOM RTSTRUCT images to NIFTI (using rt-utils)
11
'''
12
#%%
13
import SimpleITK as sitk
14
from pydicom import dcmread, FileDataset
15
from rt_utils import RTStructBuilder, RTStruct
16
import numpy as np
17
import dateutil
18
import pandas as pd
19
import os
20
import time
21
22
#%%
23
'''
24
Script to convert PET and CT dicom series to niftii files. Works under 
25
the assumption that the rescale slope and intercept in the PET dicom 
26
series map image intensities to Bq/mL. Saved PET files will have image
27
intensities of SUVbw, and saved CT files will have HU units.
28
29
'''
30
def bqml_to_suv(dcm_file: FileDataset) -> float:
31
    
32
    # Calculates the SUV conversion factor from Bq/mL to SUVbw using 
33
    # the dicom header information in one of the images from a dicom series
34
    
35
    nuclide_dose = dcm_file[0x054, 0x0016][0][0x0018, 0x1074].value  # Total injected dose (Bq)
36
    weight = dcm_file[0x0010, 0x1030].value  # Patient weight (Kg)
37
    half_life = float(dcm_file[0x054, 0x0016][0][0x0018, 0x1075].value)  # Radionuclide half life (s)
38
39
    parse = lambda x: dateutil.parser.parse(x)
40
41
    series_time = str(dcm_file[0x0008, 0x00031].value)  # Series start time (hh:mm:ss)
42
    series_date = str(dcm_file[0x0008, 0x00021].value)  # Series start date (yyy:mm:dd)
43
    series_datetime_str = series_date + ' ' + series_time
44
    series_dt = parse(series_datetime_str)
45
46
    nuclide_time = str(dcm_file[0x054, 0x0016][0][0x0018, 0x1072].value)  # Radionuclide time of injection (hh:mm:ss)
47
    nuclide_datetime_str = series_date + ' ' + nuclide_time
48
    nuclide_dt = parse(nuclide_datetime_str)
49
50
    delta_time = (series_dt - nuclide_dt).total_seconds()
51
    decay_correction = 2 ** (-1 * delta_time/half_life)
52
    suv_factor = (weight * 1000) / (decay_correction * nuclide_dose)
53
54
    return(suv_factor)
55
56
def get_filtered_roi_list(rois):
57
    filtered_rois = []
58
    for roi in rois:
59
        if roi.endswith('PETEdge'):
60
            filtered_rois.append(roi)
61
        else:
62
            pass
63
    return filtered_rois
64
65
66
def load_merge_masks(rtstruct: RTStruct) -> np.ndarray:
67
    '''
68
    Load and merge masks from a dicom RTStruct. All of the
69
    masks in the RTStruct will be merged. Add an extra line
70
    of code if you want to filter for/out certain masks.
71
    '''
72
    rois = rtstruct.get_roi_names()
73
    rois = get_filtered_roi_list(rois)
74
    masks = []
75
    for roi in rois:
76
        print(roi)
77
        mask_3d = rtstruct.get_roi_mask_by_name(roi).astype(int)
78
        masks.append(mask_3d)
79
80
    final_mask = sum(masks)  # sums element-wise
81
    final_mask = np.where(final_mask>=1, 1, 0)
82
    # Reorient the mask to line up with the reference image
83
    final_mask = np.moveaxis(final_mask, [0, 1, 2], [1, 2, 0])
84
85
    return final_mask
86
87
############################################################################################
88
########  Update the three variables below with the locations of your choice  ##############
89
############################################################################################
90
save_dir_ct = '' # path to directory where your new CT files in NIFTI format will be written
91
save_dir_pt = '' # path to directory where your new PET files in NIFTI format will be written
92
save_dir_gt = '' # path to directory where your new GT files in NIFTI format will be written
93
############################################################################################
94
############################################################################################
95
############################################################################################
96
97
cases = pd.read_csv('dicom_ctpt_to_nifti_conversion_file.csv')
98
cases = list(cases.itertuples(index=False, name=None)) 
99
structs = pd.read_csv('dicom_rtstruct_to_nifti_conversion_file.csv')
100
structs = list(structs.itertuples(index=False, name=None))
101
# Execution
102
start = time.time()
103
104
for case in cases:
105
    patient_id, ct_folder, pet_folder, convert = case
106
    if convert=='N':
107
        continue
108
    print(f'Converting patient Id: {patient_id}')
109
110
    # Convert CT series
111
    ct_reader = sitk.ImageSeriesReader()
112
    ct_series_names = ct_reader.GetGDCMSeriesFileNames(ct_folder)
113
    ct_reader.SetFileNames(ct_series_names)
114
    ct = ct_reader.Execute()
115
    sitk.WriteImage(ct, os.path.join(save_dir_ct, f"{patient_id}_0000.nii.gz"), imageIO='NiftiImageIO')
116
    print('Saved nifti CT')
117
118
    # Convert PET series
119
    pet_reader = sitk.ImageSeriesReader()
120
    pet_series_names = pet_reader.GetGDCMSeriesFileNames(pet_folder)
121
    pet_reader.SetFileNames(pet_series_names)
122
    pet = pet_reader.Execute()
123
124
    pet_img = dcmread(pet_series_names[0])  # read one of the images for header info
125
    suv_factor = bqml_to_suv(pet_img)
126
    pet = sitk.Multiply(pet, suv_factor)
127
    sitk.WriteImage(pet, os.path.join(save_dir_pt, f"{patient_id}_0001.nii.gz"), imageIO='NiftiImageIO')
128
    print('Saved nifti PET')
129
130
# Execution
131
for struct in structs:
132
    patient_id, struct_folder, ref_folder, convert = struct
133
    if convert=='N':
134
        continue
135
136
    # print('Converting RTStruct for patient {}'.format(num))
137
    # Get all the paths in order
138
    struct_file = os.listdir(struct_folder)[0]
139
    struct_path = os.path.join(struct_folder, struct_file)
140
141
    # Create the mask
142
    rtstruct = RTStructBuilder.create_from(dicom_series_path= ref_folder, rt_struct_path=struct_path)
143
    final_mask = load_merge_masks(rtstruct)
144
145
    # Load original DICOM image for reference
146
    reader = sitk.ImageSeriesReader()
147
    dicom_names = reader.GetGDCMSeriesFileNames(ref_folder)
148
    reader.SetFileNames(dicom_names)
149
    ref_img = reader.Execute()
150
151
    # Properly reference and convert the mask to an image object
152
    mask_img = sitk.GetImageFromArray(final_mask)
153
    mask_img.CopyInformation(ref_img)
154
    sitk.WriteImage(mask_img, os.path.join(save_dir_gt, f"{patient_id}.nii.gz"), imageIO="NiftiImageIO")
155
156
    print('Patient {} mask saved'.format(patient_id))