a b/utils/dicom_utils.py
1
import pydicom #for loading dicom
2
import SimpleITK as sitk #for loading mhd/raw
3
import os
4
import numpy as np
5
import scipy.ndimage
6
7
#DICOM: send path to any *.dcm file where containing dir has the other slices (dcm files), or path to the dir itself
8
#MHD/RAW: send path to the *.mhd file where containing die has the coorisponding *.raw file
9
def load_scan(path2scan):
10
    if (path2scan.split('.')[-1] == 'mhd') or (path2scan.split('.')[-1] == 'raw'):
11
        return load_mhd(path2scan)
12
    elif path2scan.split('.')[-1] == 'dcm':
13
        return load_dicom(os.path.split(path2scan)[0]) #pass containing directory
14
    elif os.listdir(path2scan)[0].split('.')[-1] == 'dcm':
15
        return load_dicom(path2scan)
16
    else:
17
        raise Exception('No valid scan [series] found in given file/directory')
18
19
20
def load_mhd(path2scan):
21
    itkimage = sitk.ReadImage(path2scan)
22
    scan = sitk.GetArrayFromImage(itkimage)
23
    spacing = np.flip(np.array(itkimage.GetSpacing()),axis=0)
24
    orientation = np.transpose(np.array(itkimage.GetDirection()).reshape((3, 3)))
25
    origin = np.flip(np.array(itkimage.GetOrigin()),axis=0) #origionally in yxz format (read xy in viewers but sanved as yx)
26
    return scan, spacing, orientation, origin, None #output in zyx format
27
28
def load_dicom(path2scan_dir):
29
    dicom_folder = path2scan_dir
30
    dcms = os.listdir(dicom_folder)
31
    first_slice_data = pydicom.read_file(path2scan_dir + '\\' + dcms[0])
32
    first_slice = first_slice_data.pixel_array
33
    orientation = np.transpose(first_slice_data.ImageOrientationPatient) #zyx format
34
    spacing_xy = np.array(first_slice_data.PixelSpacing, dtype=float)
35
    spacing_z = np.float(first_slice_data.SliceThickness)
36
    spacing = np.array([spacing_z, spacing_xy[1], spacing_xy[0]]) #zyx format
37
38
    scan = np.zeros((len(dcms),first_slice.shape[0],first_slice.shape[1]))
39
    raw_slices=[]
40
    indexes = []
41
    for dcm in dcms:
42
        slice_data = pydicom.read_file(dicom_folder + '\\' + dcm)
43
        slice_data.filename = dcm
44
        raw_slices.append(slice_data)
45
        indexes.append(float(slice_data.ImagePositionPatient[2]))
46
    indexes = np.array(indexes,dtype=float)
47
48
    raw_slices = [x for _, x in sorted(zip(indexes, raw_slices))]
49
    origin = np.array(raw_slices[0][0x00200032].value) #origin is assumed to be the image location of the first slice
50
    if origin is None:
51
        origin = np.zeros(3)
52
    else:
53
        origin = np.array([origin[2],origin[1],origin[0]]) #change from x,y,z to z,y,x
54
55
    for i, slice in enumerate(raw_slices):
56
        scan[i, :, :] = slice.pixel_array
57
    # for i, index in enumerate(indexes):
58
    #     for slice in raw_slices:
59
    #         if int(slice.InstanceNumber) == index:
60
    #             scan[i,:,:] = slice._pixel_data_numpy()
61
    return scan, spacing, orientation, origin, raw_slices
62
63
64
#point to directory of folders conting dicom scans only (subdirs only), runs aon all folders..
65
# ref directory is used to copy the m
66
def save_dicom(scan, origional_raw_slices, dst_directory): # \\dfds\\ format
67
    os.makedirs(dst_directory,exist_ok=True)
68
    for i, slice in enumerate(origional_raw_slices):
69
        slice.pixel_array.flat = scan[i,:,:].flat
70
        slice.PixelData = slice.pixel_array.tobytes()
71
        slice.save_as(os.path.join(dst_directory,slice.filename))#.dcmwrite(os.path.join(dst_directory,slice.filename),slice)
72
73
#img_array: 3d numpy matrix, z,y,x
74
def toDicom(save_dir, img_array,  pixel_spacing, orientation):
75
    ref_scan = pydicom.read_file('utils/ref_scan.dcm') #slice from soem real scan so we can copy the meta data
76
77
    #write dcm file for each slice in scan
78
    for i, slice in enumerate(img_array):
79
        ref_scan.pixel_array.flat = img_array[i,:,:].flat
80
        ref_scan.PixelData = ref_scan.pixel_array.tobytes()
81
        ref_scan.RefdSOPInstanceUID = str(i)
82
        ref_scan.SOPInstanceUID = str(i)
83
        ref_scan.InstanceNumber = str(i)
84
        ref_scan.SliceLocation = str(i)
85
        ref_scan.ImagePositionPatient[2] = str(i*pixel_spacing[0])
86
        ref_scan.RescaleIntercept = 0
87
        ref_scan.Rows = img_array.shape[1]
88
        ref_scan.Columns = img_array.shape[2]
89
        ref_scan.PixelSpacing = [str(pixel_spacing[2]),str(pixel_spacing[1])]
90
        ref_scan.SliceThickness = pixel_spacing[0]
91
        #Pixel Spacing                       DS: ['0.681641', '0.681641']
92
        #Image Position (Patient)            DS: ['-175.500000', '-174.500000', '49']
93
        #Image Orientation (Patient)         DS: ['1.000000', '0.000000', '0.000000', '0.000000', '1.000000', '0.000000']
94
        #Rows                                US: 512
95
        #Columns                             US: 512
96
        os.makedirs(save_dir,exist_ok=True)
97
        ref_scan.save_as(os.path.join(save_dir,str(i)+'.dcm'))
98
99
def scale_scan(scan,spacing,factor=1):
100
    resize_factor = factor * spacing
101
    new_real_shape = scan.shape * resize_factor
102
    new_shape = np.round(new_real_shape)
103
    real_resize_factor = new_shape / scan.shape
104
    new_spacing = spacing / real_resize_factor
105
    scan_resized = scipy.ndimage.interpolation.zoom(scan, real_resize_factor, mode='nearest')
106
    return scan_resized, resize_factor
107
108
# get the shape of an orthotope in 1:1:1 ratio AFTER scaling to the given spacing
109
def get_scaled_shape(shape, spacing):
110
    new_real_shape = shape * spacing
111
    return np.round(new_real_shape).astype(int)
112
113
def scale_vox_coord(coord, spacing, factor=1):
114
    resize_factor = factor * spacing
115
    return (coord*resize_factor).astype(int)
116
117
def world2vox(world_coord, spacing, orientation, origin):
118
    world_coord = np.dot(np.linalg.inv(np.dot(orientation, np.diag(spacing))), world_coord - origin)
119
    if orientation[0, 0] < 0:
120
        vox_coord = (np.array([world_coord[0], world_coord[2], world_coord[1]])).astype(int)
121
    else:
122
        vox_coord = (np.array([world_coord[0], world_coord[1], world_coord[2]])).astype(int)
123
    return vox_coord