|
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 |