├── .gitignore
├── README.md
├── ParamsSettings
└── Pyradiomics_Params.yaml
├── RadiomicsOntology
└── ORAW_RO_Table.csv
├── PyrexReader.py
└── PyrexOutput.py
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | .idea/
3 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Py-rex (Pyradiomics extension) Version 2.1
2 |
3 | This is an open-source python package for [pyradiomics](https://github.com/Radiomics/pyradiomics) extension on both input and output sides.
4 | With this pacakge we aim to allow users to use origial DICOM file and RTSTRUCT for radiomics calculation.
5 |
6 | ### Features
7 | 1. Py-rex is allowed users employ original DICOM files and RTstruture;
8 | 2. Internal module for creation of ROI binary mask;
9 | 3. Computation of radiomic features on a specific ROI;
10 | 4. Semi-automatic approach to handle multiple ROI names;
11 | 5. Radiomic features output in different formats (e.g., rdf, text and csv) with related ontologies (e.g., [Radiomics Ontology](https://bioportal.bioontology.org/ontologies/ROO) and [Radiation Oncology Ontology](https://bioportal.bioontology.org/ontologies/RO)).
12 | 6. Applicable for full modalities (CT, PET and MR)
13 | 7. Allow batch processing.
14 |
15 |
16 | ### License
17 |
18 | Py-rex may not be used for commercial purposes. This package is freely available to browse, download, and use for scientific
19 | and educational purposes as outlined in the [Creative Commons Attribution 3.0 Unported License](https://creativecommons.org/licenses/by/3.0/).
20 |
21 | ### Developers
22 | - [Zhenwei Shi](https://github.com/zhenweishi)1,2
23 | - [Leonard Wee]1,2
24 | - [Andre Dekker]1,2
25 |
26 | 1Department of Radiation Oncology (MAASTRO Clinic), Maastricht, The Netherlands,
27 | 2GROW-School for Oncology and Developmental Biology, Maastricht University Medical Center, Maastricht, The Netherlands,
28 |
29 |
--------------------------------------------------------------------------------
/ParamsSettings/Pyradiomics_Params.yaml:
--------------------------------------------------------------------------------
1 | # ############################# Extracted using PyRadiomics version: 2.1.0 ######################################
2 |
3 | imageType:
4 | Original:
5 | binWidth: 0.5
6 | LoG:
7 | binWidth: 10
8 | sigma: [1.0, 2.0, 3.0]
9 | Wavelet:
10 | binWidth: 5
11 |
12 | featureClass:
13 |
14 | shape: # Remove VoxelVolume, correlated to Volume
15 | - Elongation
16 | - Flatness
17 | - LeastAxisLength
18 | - MajorAxisLength
19 | - Maximum2DDiameterColumn
20 | - Maximum2DDiameterRow
21 | - Maximum2DDiameterSlice
22 | - Maximum3DDiameter
23 | - MeshVolume
24 | - MinorAxisLength
25 | - Sphericity
26 | - SurfaceArea
27 | - SurfaceVolumeRatio
28 | firstorder: # Remove Total Energy, correlated to Energy (due to resampling enabled)
29 | - 10Percentile
30 | - 90Percentile
31 | - Energy
32 | - Entropy
33 | - InterquartileRange
34 | - Kurtosis
35 | - Maximum
36 | - Mean
37 | - MeanAbsoluteDeviation
38 | - Median
39 | - Minimum
40 | - Range
41 | - RobustMeanAbsoluteDeviation
42 | - RootMeanSquared
43 | - Skewness
44 | - Uniformity
45 | - Variance
46 | glcm: # Disable SumAverage by specifying all other GLCM features available
47 | - 'Autocorrelation'
48 | - 'JointAverage'
49 | - 'ClusterProminence'
50 | - 'ClusterShade'
51 | - 'ClusterTendency'
52 | - 'Contrast'
53 | - 'Correlation'
54 | - 'DifferenceAverage'
55 | - 'DifferenceEntropy'
56 | - 'DifferenceVariance'
57 | - 'JointEnergy'
58 | - 'JointEntropy'
59 | - 'Imc1'
60 | - 'Imc2'
61 | - 'Idm'
62 | - 'Idmn'
63 | - 'Id'
64 | - 'Idn'
65 | - 'InverseVariance'
66 | - 'MaximumProbability'
67 | - 'SumEntropy'
68 | - 'SumSquares'
69 | glrlm:
70 | glszm:
71 | gldm:
72 | ngtdm:
73 |
74 | setting:
75 | # Resampling:
76 | # Usual spacing for CT is often close to 1 or 2 mm, if very large slice thickness is used,
77 | # increase the resampled spacing.
78 | # On a side note: increasing the resampled spacing forces PyRadiomics to look at more coarse textures, which may or
79 | # may not increase accuracy and stability of your extracted features.
80 | interpolator: 'sitkBSpline'
81 | resampledPixelSpacing: [2, 2, 2]
82 | padDistance: 10 # Extra padding for large sigma valued LoG filtered images
83 |
84 | # Mask validation:
85 | # correctMask and geometryTolerance are not needed, as both image and mask are resampled, if you expect very small
86 | # masks, consider to enable a size constraint by uncommenting settings below:
87 | #minimumROIDimensions: 2
88 | #minimumROISize: 50
89 |
90 | # Resegmentation: remove outliers >3 std from the mean (affects all classes except shape)
91 | resegmentRange: [-3, 3]
92 | resegmentMode: sigma
93 |
94 | # Image discretization:
95 | # The ideal number of bins is somewhere in the order of 16-128 bins. A possible way to define a good binwidt is to
96 | # extract firstorder:Range from the dataset to analyze, and choose a binwidth so, that range/binwidth remains approximately
97 | # in this range of bins.
98 | # THIS IS DEFINED AT THE IMAGE TYPE LEVEL
99 |
100 | # first order specific settings:
101 | voxelArrayShift: 1000 # Minimum value in HU is -1000, shift +1000 to prevent negative values from being squared.
102 |
103 | # Misc:
104 | # default label value. Labels can also be defined in the call to featureextractor.execute, as a commandline argument,
105 | # or in a column "Label" in the input csv (batchprocessing)
106 | label: 1
107 |
--------------------------------------------------------------------------------
/RadiomicsOntology/ORAW_RO_Table.csv:
--------------------------------------------------------------------------------
1 | Pyradiomics Name,RO URI
2 | firstorder_10Percentile,www.radiomics.org/RO/GPMT
3 | firstorder_90Percentile,www.radiomics.org/RO/OZ0C
4 | firstorder_Energy,www.radiomics.org/RO/8ZQL
5 | firstorder_Entropy,www.radiomics.org/RO/TLU2
6 | firstorder_InterquartileRange,www.radiomics.org/RO/WR0O
7 | firstorder_Kurtosis,www.radiomics.org/RO/C3I7
8 | firstorder_Maximum,www.radiomics.org/RO/3NCY
9 | firstorder_Mean,www.radiomics.org/RO/X6K6
10 | firstorder_MeanAbsoluteDeviation,www.radiomics.org/RO/D2ZX
11 | firstorder_Median,www.radiomics.org/RO/WIFQ
12 | firstorder_Minimum,www.radiomics.org/RO/1PR8
13 | firstorder_Range,www.radiomics.org/RO/5Z3W
14 | firstorder_RobustMeanAbsoluteDeviation,www.radiomics.org/RO/WRZB
15 | firstorder_RootMeanSquared,www.radiomics.org/RO/5ZWQ
16 | firstorder_Skewness,www.radiomics.org/RO/88K1
17 | firstorder_TotalEnergy,www.radiomics.org/RO/2490
18 | firstorder_Uniformity,www.radiomics.org/RO/BJ5W
19 | firstorder_Variance,www.radiomics.org/RO/CH89
20 | glcm_Autocorrelation,www.radiomics.org/RO/QWB0
21 | glcm_ClusterProminence,www.radiomics.org/RO/AE86
22 | glcm_ClusterShade,www.radiomics.org/RO/7NFM
23 | glcm_ClusterTendency,www.radiomics.org/RO/DG8W
24 | glcm_Contrast,www.radiomics.org/RO/ACUI
25 | glcm_Correlation,www.radiomics.org/RO/NI2N
26 | glcm_DifferenceAverage,www.radiomics.org/RO/TF7R
27 | glcm_DifferenceEntropy,www.radiomics.org/RO/NTRS
28 | glcm_DifferenceVariance,www.radiomics.org/RO/D3YU
29 | glcm_Id,www.radiomics.org/RO/IB1Z
30 | glcm_Idm,www.radiomics.org/RO/WF0Z
31 | glcm_Idmn,www.radiomics.org/RO/1QCO
32 | glcm_Idn,www.radiomics.org/RO/NDRX
33 | glcm_Imc1,www.radiomics.org/RO/R8DG
34 | glcm_Imc2,www.radiomics.org/RO/JN9H
35 | glcm_InverseVariance,www.radiomics.org/RO/E8JP
36 | glcm_JointAverage,www.radiomics.org/RO/60VM
37 | glcm_JointEnergy,www.radiomics.org/RO/8ZQL
38 | glcm_JointEntropy,www.radiomics.org/RO/TU9B
39 | glcm_MaximumProbability,www.radiomics.org/RO/GYBY
40 | glcm_SumAverage,www.radiomics.org/RO/ZGXS
41 | glcm_SumEntropy,www.radiomics.org/RO/P6QZ
42 | glcm_SumSquares,www.radiomics.org/RO/OEEB
43 | gldm_DependenceEntropy,www.radiomics.org/RO/GBDU
44 | gldm_DependenceNonUniformity,www.radiomics.org/RO/V294
45 | gldm_DependenceNonUniformityNormalized,www.radiomics.org/RO/IATH
46 | gldm_DependenceVariance,www.radiomics.org/RO/7WT1
47 | gldm_GrayLevelNonUniformity,www.radiomics.org/RO/VFT7
48 | gldm_GrayLevelVariance,www.radiomics.org/RO/QK93
49 | gldm_HighGrayLevelEmphasis,www.radiomics.org/RO/K26C
50 | gldm_LargeDependenceEmphasis,www.radiomics.org/RO/MB4I
51 | gldm_LargeDependenceHighGrayLevelEmphasis,www.radiomics.org/RO/KLTH
52 | gldm_LargeDependenceLowGrayLevelEmphasis,www.radiomics.org/RO/A7WM
53 | gldm_LowGrayLevelEmphasis,www.radiomics.org/RO/S1RA
54 | gldm_SmallDependenceEmphasis,www.radiomics.org/RO/0GBI
55 | gldm_SmallDependenceHighGrayLevelEmphasis,www.radiomics.org/RO/DKNJ
56 | gldm_SmallDependenceLowGrayLevelEmphasis,www.radiomics.org/RO/RUVG
57 | glrlm_GrayLevelNonUniformity,www.radiomics.org/RO/R5YN
58 | glrlm_GrayLevelNonUniformityNormalized,www.radiomics.org/RO/OVBL
59 | glrlm_GrayLevelVariance,www.radiomics.org/RO/8CE5
60 | glrlm_HighGrayLevelRunEmphasis,www.radiomics.org/RO/G3QZ
61 | glrlm_LongRunEmphasis,www.radiomics.org/RO/W4KF
62 | glrlm_LongRunHighGrayLevelEmphasis,www.radiomics.org/RO/3KUM
63 | glrlm_LongRunLowGrayLevelEmphasis,www.radiomics.org/RO/IVPO
64 | glrlm_LowGrayLevelRunEmphasis,www.radiomics.org/RO/V3SW
65 | glrlm_RunEntropy,www.radiomics.org/RO/HJ9O
66 | glrlm_RunLengthNonUniformity,www.radiomics.org/RO/W92Y
67 | glrlm_RunLengthNonUniformityNormalized,www.radiomics.org/RO/IC23
68 | glrlm_RunPercentage,www.radiomics.org/RO/9ZK5
69 | glrlm_RunVariance,www.radiomics.org/RO/SXLW
70 | glrlm_ShortRunEmphasis,www.radiomics.org/RO/22OV
71 | glrlm_ShortRunHighGrayLevelEmphasis,www.radiomics.org/RO/GD3A
72 | glrlm_ShortRunLowGrayLevelEmphasis,www.radiomics.org/RO/HTZT
73 | glszm_GrayLevelNonUniformity,www.radiomics.org/RO/JNSA
74 | glszm_GrayLevelNonUniformityNormalized,www.radiomics.org/RO/Y1RO
75 | glszm_GrayLevelVariance,www.radiomics.org/RO/BYLV
76 | glszm_HighGrayLevelZoneEmphasis,www.radiomics.org/RO/5GN9
77 | glszm_LargeAreaEmphasis,www.radiomics.org/RO/48P8
78 | glszm_LargeAreaHighGrayLevelEmphasis,www.radiomics.org/RO/J17V
79 | glszm_LargeAreaLowGrayLevelEmphasis,www.radiomics.org/RO/YH51
80 | glszm_LowGrayLevelZoneEmphasis,www.radiomics.org/RO/XMSY
81 | glszm_SizeZoneNonUniformity,www.radiomics.org/RO/4JP3
82 | glszm_SizeZoneNonUniformityNormalized,www.radiomics.org/RO/VB3A
83 | glszm_SmallAreaEmphasis,www.radiomics.org/RO/5QRC
84 | glszm_SmallAreaHighGrayLevelEmphasis,www.radiomics.org/RO/HW1V
85 | glszm_SmallAreaLowGrayLevelEmphasis,www.radiomics.org/RO/5RAI
86 | glszm_ZoneEntropy,www.radiomics.org/RO/GU8N
87 | glszm_ZonePercentage,www.radiomics.org/RO/P30P
88 | glszm_ZoneVariance,www.radiomics.org/RO/3NSA
89 | ngtdm_Busyness,www.radiomics.org/RO/NQ30
90 | ngtdm_Coarseness,www.radiomics.org/RO/QCDE
91 | ngtdm_Complexity,www.radiomics.org/RO/HDEZ
92 | ngtdm_Contrast,www.radiomics.org/RO/65HE
93 | ngtdm_Strength,www.radiomics.org/RO/1X9X
94 | shape_Maximum3DDiameter,www.radiomics.org/RO/L0JK
95 | shape_MeshVolume,www.radiomics.org/RO/RNU0
96 | shape_MajorAxisLength,www.radiomics.org/RO/TDIC
97 | shape_Sphericity,www.radiomics.org/RO/QCFX
98 | shape_LeastAxisLength,www.radiomics.org/RO/7J51
99 | shape_Elongation,www.radiomics.org/RO/Q3CK
100 | shape_SurfaceVolumeRatio,www.radiomics.org/RO/2PR5
101 | shape_Maximum2DDiameterSlice,www.radiomics.org/RO/2130
102 | shape_Flatness,www.radiomics.org/RO/N17B
103 | shape_SurfaceArea,www.radiomics.org/RO/C0JK
104 | shape_MinorAxisLength,www.radiomics.org/RO/P9VJ
105 | shape_Maximum2DDiameterColumn,www.radiomics.org/RO/2150
106 | shape_Maximum2DDiameterRow,www.radiomics.org/RO/2140
107 | shape_VoxelVolume,www.radiomics.org/RO/RNU0
108 | glcm_MCC,www.radiomics.org/RO/xxx
109 |
--------------------------------------------------------------------------------
/PyrexReader.py:
--------------------------------------------------------------------------------
1 | """
2 | ###############################
3 | @author: zhenwei.shi, Maastro##
4 | ###############################
5 | Usage:
6 | import PyrexReader
7 |
8 | img_path = '.\CTscan'
9 | rtstruct_path = '.\RTstruct'
10 | ROI = Region Of Interest
11 | Img,Mask = PyrexReader.Img_Bimask(img_path,rtstruct_path,ROI)
12 |
13 | """
14 |
15 | import pydicom,os
16 | import numpy as np
17 | from skimage import draw
18 | import SimpleITK as sitk
19 | import re
20 | import glob
21 |
22 | # module PyrexReader:
23 | def match_ROIid(rtstruct_path,ROI_name): # Match ROI id in RTSTURCT to a given ROI name in the parameter file
24 | mask_vol = Read_RTSTRUCT(rtstruct_path)
25 | M= mask_vol[0]
26 | for i in range(len(M.StructureSetROISequence)):
27 | if str(ROI_name)==M.StructureSetROISequence[i].ROIName:
28 | ROI_number = M.StructureSetROISequence[i].ROINumber
29 | break
30 | for ROI_id in range(len(M.StructureSetROISequence)):
31 | if ROI_number == M.ROIContourSequence[ROI_id].ReferencedROINumber:
32 | break
33 | return ROI_id
34 |
35 | def ROI_match(ROI,rtstruct_path): # Literal match ROI
36 | mask_vol=Read_RTSTRUCT(rtstruct_path)
37 | M=mask_vol[0]
38 | target = []
39 | for i in range(0,len(M.StructureSetROISequence)):
40 | if re.search(ROI,M.StructureSetROISequence[i].ROIName):
41 | target.append(M.StructureSetROISequence[i].ROIName)
42 | if len(target)==0:
43 | for j in range(0,len(M.StructureSetROISequence)):
44 | print(M.StructureSetROISequence[j].ROIName)
45 | break
46 | print('Input ROI is: ')
47 | ROI_name = raw_input()
48 | target.append(ROI_name)
49 | print('------------------------------------')
50 | return target
51 |
52 | def Read_scan(path): # Read scans under the specified path
53 | scan = [pydicom.dcmread(s, force=True) for s in glob.glob(os.path.join(path,'*.dcm'))]
54 | try:
55 | scan.sort(key = lambda x: int(x.ImagePositionPatient[2])) # sort slices based on Z coordinate
56 | except:
57 | print('AttributeError: Cannot read scans')
58 | return scan
59 |
60 | def Read_RTSTRUCT(path): # Read RTSTRUCT under the specified path
61 | try:
62 | rt = [pydicom.dcmread(s, force=True) for s in glob.glob(os.path.join(path,'*.dcm'))]
63 | except:
64 | print('AttributeError: Cannot read RTSTRUCT')
65 | return rt
66 |
67 | def poly2mask(vertex_row_coords, vertex_col_coords, shape): # Mask interpolation
68 | fill_row_coords, fill_col_coords = draw.polygon(vertex_row_coords, vertex_col_coords, shape)
69 | mask = np.zeros(shape, dtype=np.bool)
70 | mask[fill_row_coords, fill_col_coords] = True
71 | return mask
72 |
73 | def get_pixels_hu(scans): # convert to Hounsfield Unit (HU) by multiplying rescale slope and adding intercept
74 | image = np.stack([s.pixel_array for s in scans])
75 | image = image.astype(np.int16) #convert to int16
76 | # the code below checks if the image has slope and intercept
77 | # since MRI images often do not provide these
78 | try:
79 | intercept = scans[0].RescaleIntercept
80 | slope = scans[0].RescaleSlope
81 | except AttributeError:
82 | pass
83 | else:
84 | if slope != 1:
85 | image = slope * image.astype(np.float64)
86 | image = image.astype(np.int16)
87 | image += np.int16(intercept)
88 | return np.array(image, dtype=np.int16)
89 |
90 | def Img_Bimask(img_path,rtstruct_path,ROI_name): # generating image array and binary mask
91 | print('Generating binary mask based on ROI: %s ......' % ROI_name)
92 | img_vol = Read_scan(img_path)
93 | mask_vol=Read_RTSTRUCT(rtstruct_path)
94 | IM=img_vol[0] # Slices usually have the same basic information including slice size, patient position, etc.
95 | IM_P=get_pixels_hu(img_vol)
96 | M=mask_vol[0]
97 | num_slice=len(img_vol)
98 | mask=np.zeros([num_slice, IM.Rows, IM.Columns],dtype=np.uint8)
99 | xres=np.array(IM.PixelSpacing[0])
100 | yres=np.array(IM.PixelSpacing[1])
101 | slice_thickness=np.abs(img_vol[1].ImagePositionPatient[2]-img_vol[0].ImagePositionPatient[2])
102 |
103 | ROI_id = match_ROIid(rtstruct_path,ROI_name)
104 | #Check DICOM file Modality
105 | if IM.Modality == 'CT' or 'PT':
106 | for k in range(len(M.ROIContourSequence[ROI_id].ContourSequence)):
107 | Cpostion_rt = M.ROIContourSequence[ROI_id].ContourSequence[k].ContourData[2]
108 | for i in range(num_slice):
109 | if np.int64(Cpostion_rt) == np.int64(img_vol[i].ImagePositionPatient[2]): # match the binary mask and the corresponding slice
110 | sliceOK = i
111 | break
112 | x=[]
113 | y=[]
114 | z=[]
115 | m=M.ROIContourSequence[ROI_id].ContourSequence[k].ContourData
116 | for i in range(0,len(m),3):
117 | x.append(m[i+1])
118 | y.append(m[i+0])
119 | z.append(m[i+2])
120 | x=np.array(x)
121 | y=np.array(y)
122 | z=np.array(z)
123 | x-= IM.ImagePositionPatient[1]
124 | y-= IM.ImagePositionPatient[0]
125 | z-= IM.ImagePositionPatient[2]
126 | pts = np.zeros([len(x),3])
127 | pts[:,0] = x
128 | pts[:,1] = y
129 | pts[:,2] = z
130 | a=0
131 | b=1
132 | p1 = xres
133 | p2 = yres
134 | m=np.zeros([2,2])
135 | m[0,0]=img_vol[sliceOK].ImageOrientationPatient[a]*p1
136 | m[0,1]=img_vol[sliceOK].ImageOrientationPatient[a+3]*p2
137 | m[1,0]=img_vol[sliceOK].ImageOrientationPatient[b]*p1
138 | m[1,1]=img_vol[sliceOK].ImageOrientationPatient[b+3]*p2
139 | # Transform points from reference frame to image coordinates
140 | m_inv=np.linalg.inv(m)
141 | pts = (np.matmul((m_inv),(pts[:,[a,b]]).T)).T
142 | mask[sliceOK,:,:] = np.logical_or(mask[sliceOK,:,:],poly2mask(pts[:,0],pts[:,1],[IM_P.shape[1],IM_P.shape[2]]))
143 | elif IM.Modality == 'MR':
144 | slice_0 = img_vol[0]
145 | slice_n = img_vol[-1]
146 |
147 | # the screen coordinates, including the slice number can then be computed
148 | # using the inverse of this matrix
149 | transform_matrix = np.r_[slice_0.ImageOrientationPatient[3:], 0, slice_0.ImageOrientationPatient[:3], 0, 0, 0, 0, 0, 1, 1, 1, 1].reshape(4, 4).T # yeah that's ugly but I didn't have enough time to make anything nicer
150 | T_0 = np.array(slice_0.ImagePositionPatient)
151 | T_n = np.array(slice_n.ImagePositionPatient)
152 | col_2 = (T_0 - T_n) / (1 - len(img_vol))
153 | pix_s = slice_0.PixelSpacing
154 | transform_matrix[:, -1] = np.r_[T_0, 1]
155 | transform_matrix[:, 2] = np.r_[col_2, 0]
156 | transform_matrix[:, 0] *= pix_s[1]
157 | transform_matrix[:, 1] *= pix_s[0]
158 |
159 | transform_matrix = np.linalg.inv(transform_matrix)
160 | for s in M.ROIContourSequence[ROI_id].ContourSequence:
161 | Cpostion_rt = np.r_[s.ContourData[:3], 1]
162 | roi_slice_nb = int(transform_matrix.dot(Cpostion_rt)[2])
163 | for i in range(num_slice):
164 | print(roi_slice_nb, i)
165 | if roi_slice_nb == i:
166 | sliceOK = i
167 | break
168 | x=[]
169 | y=[]
170 | z=[]
171 | m=s.ContourData
172 | for i in range(0,len(m),3):
173 | x.append(m[i+1])
174 | y.append(m[i+0])
175 | z.append(m[i+2])
176 | x=np.array(x)
177 | y=np.array(y)
178 | z=np.array(z)
179 | x-= IM.ImagePositionPatient[1]
180 | y-= IM.ImagePositionPatient[0]
181 | z-= IM.ImagePositionPatient[2]
182 | pts = np.zeros([len(x),3])
183 | pts[:,0] = x
184 | pts[:,1] = y
185 | pts[:,2] = z
186 | a=0
187 | b=1
188 | p1 = xres
189 | p2 = yres
190 | m=np.zeros([2,2])
191 | m[0,0]=img_vol[sliceOK].ImageOrientationPatient[a]*p1
192 | m[0,1]=img_vol[sliceOK].ImageOrientationPatient[a+3]*p2
193 | m[1,0]=img_vol[sliceOK].ImageOrientationPatient[b]*p1
194 | m[1,1]=img_vol[sliceOK].ImageOrientationPatient[b+3]*p2
195 | # Transform points from reference frame to image coordinates
196 | m_inv=np.linalg.inv(m)
197 | pts = (np.matmul((m_inv),(pts[:,[a,b]]).T)).T
198 | mask[sliceOK,:,:] = np.logical_or(mask[sliceOK,:,:],poly2mask(pts[:,0],pts[:,1],[IM_P.shape[1],IM_P.shape[2]]))
199 |
200 | # The pixel intensity values are normalized to range [0 255] using linear translation
201 | IM_P=IM_P.astype(np.float32)
202 | # IM_P = (IM_P-np.min(IM_P))*255/(np.max(IM_P)-np.min(IM_P))
203 |
204 | Img=sitk.GetImageFromArray(IM_P) # convert image_array to image
205 | Mask=sitk.GetImageFromArray(mask)
206 | # try:
207 | # origin = IM.GetOrigin()
208 | # except:
209 | # origin = (0.0, 0.0, 0.0)
210 |
211 | # Set voxel spacing [[pixel spacing_x, pixel spacing_y, slice thickness]
212 | #slice_thickness = IM.SliceThickness
213 | Img.SetSpacing([np.float64(xres),np.float64(yres),np.float64(slice_thickness)])
214 | Mask.SetSpacing([np.float64(xres),np.float64(yres),np.float64(slice_thickness)])
215 |
216 | return Img, Mask
217 |
--------------------------------------------------------------------------------
/PyrexOutput.py:
--------------------------------------------------------------------------------
1 | """
2 | ###############################
3 | @author: zhenwei.shi, Maastro##
4 | ###############################
5 | """
6 |
7 | from rdflib import Graph, Literal
8 | from rdflib.namespace import Namespace,URIRef,RDF,RDFS
9 | import urllib
10 | import os
11 | import csv
12 | from datetime import datetime
13 | import pandas as pd
14 |
15 | # Function to store readiomics in different types of formats, such as csv and RDF.
16 | def RadiomicsRDF(featureVector,exportDir,patientID,myStructUID,ROI,export_format,export_name):
17 | graph = Graph() # Create a rdflib graph object
18 | # feature_name = [] # Create a list for features
19 | # feature_uri = [] # Create a list for feature uri (ontology)
20 |
21 | # Namespaces used in O-RAW
22 | ro = Namespace('http://www.radiomics.org/RO/')
23 | roo = Namespace('http://www.cancerdata.org/roo/')
24 | IAO = Namespace('http://purl.obolibary.org/obo/IAO_')
25 | SWO = Namespace('http://www.ebi.ac.uk/swo/SWO_')
26 | NCIT = Namespace('http://ncicb.nci.nih.gov/xml/owl/EVS/Thesaurus.owl#')
27 | # Adding namespace to graph space
28 | graph.bind('ro',ro)
29 | graph.bind('roo',roo)
30 | graph.bind('IAO',IAO)
31 | graph.bind('SWO',SWO)
32 | graph.bind('NCIT',NCIT)
33 | # ------------------------- URI of related entities -----------------
34 | # ^^^^^^^^^^^^^^^^^^^^^^^^^ Level-1 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
35 | patient_uri = URIRef(NCIT+'C16960')
36 | has_pacs_study = URIRef(roo + '100284') # patient has_pacs_study scan
37 | scan_uri = URIRef(NCIT+'C17999')
38 | converted_to = URIRef(ro + '0310') # scan converted_to image_volume
39 | image_volume_uri = URIRef(ro + '0271')
40 | is_part_of = URIRef(ro + '0298') # image_volume is_part_of image_space
41 | image_space_uri = URIRef(ro + '0225')
42 | # ROImask_uri = URIRef(roo + '0272') # ROImask is_part_of image_space
43 | is_label_of = URIRef(ro + 'P00190') # GTV/... is_label_of ROImask
44 | has_label = URIRef(ro+'P00051')
45 | # GTV_uri = URIRef(roo + '100006')
46 | used_to_compute = URIRef(ro + '0296') # image_space used_to_compute RadiomicsFeature
47 |
48 | # ^^^^^^^^^^^^^^^^^^^^^^^^^ Level-2 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
49 | mm_uri = URIRef(ro + 'I0020')
50 | mm2_uri = URIRef(ro + 'I0027')
51 | mm3_uri = URIRef(ro + 'I0011')
52 | has_value = URIRef(ro + '010191') # RadiomicsFeature has_value
53 | has_unit = URIRef(ro + '010198') # RadiomicsFeature has_unit
54 |
55 | # ^^^^^^^^^^^^^^^^^^^^^^^^^ Level-3 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
56 | computed_using = URIRef(ro + 'P00002') # RadiomicsFeature computed_using calculationrun_space
57 | calculationrun_space_uri = URIRef(ro + '0297')
58 | # run_on = URIRef(ro + '00000002') # calclulationrun run_on datetime
59 | at_date_time = URIRef(roo + '100041')
60 | performed_by = URIRef(ro + '0283') # calculationrun_space performed_by softwareproperties_uri
61 | softwareproperties_uri = URIRef(ro + '010215') # software has_label literal(SoftwareProperties)
62 | has_programming_language = URIRef(ro + '0010195') # software has_programming_language programminglanguage
63 | # programminglanguage_uri = URIRef(IAO + '0000025')
64 | # python_uri = URIRef(SWO + '000018')
65 | has_version = URIRef(ro + '0010192') # software has_version
66 | # version_uri = URIRef(ro + '010166')
67 |
68 | # ^^^^^^^^^^^^^^^^^^^^^^^^^ Level-4 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
69 | featureparameterspace_uri = URIRef(ro + '001000')
70 | defined_by = URIRef(ro + 'P000009') # featureparameterspace defined_by settings
71 |
72 | # filterproperties_uri = URIRef(roo + '0255') # has_value wavelet right/not?
73 | # aggregationparameters = URIRef(roo + '0218')
74 | # discretizationparameters = URIRef(roo + '0214')
75 | # featureSpecificparameters = URIRef(roo + '0215')
76 | # interpolationparameters = URIRef(roo + '0217')
77 | # reSegmentationparameters = URIRef(roo + '0216')
78 | # -------------- localhost URIs ---------------------------
79 | localhost_patient = 'http://localhost/data/patient_'
80 | localhost_scan = 'http://localhost/data/scan_'
81 | localhost_imagevolume = 'http://localhost/data/imagevolume_'
82 | localhost_imagespace = 'http://localhost/data/imagespace_'
83 | localhost_ROI = 'http://localhost/data/ROI_'
84 | localhost_feature = 'http://localhost/data/feature_'
85 | localhost_featureparameter = 'http://localhost/data/localhost_featureparameter_'
86 | #-----------------------
87 | localhost_mm = 'http://localhost/data/mm'
88 | localhost_mm2 = 'http://localhost/data/mm2'
89 | localhost_mm3 = 'http://localhost/data/mm3'
90 |
91 | #------------------------RDF entities---------------------------------
92 | RDF_patid = URIRef(localhost_patient+patientID)
93 | RDF_scan = URIRef(localhost_scan + myStructUID)
94 | RDF_imagevolume = URIRef(localhost_imagevolume + myStructUID + '_' + urllib.quote(ROI))
95 | RDF_imagespace = URIRef(localhost_imagespace + myStructUID + '_' + urllib.quote(ROI))
96 | RDF_featureparameter = URIRef(localhost_featureparameter + myStructUID + '_' + urllib.quote(ROI))
97 | RDF_ROI = URIRef(localhost_ROI+ '_' + urllib.quote(ROI))
98 | RDF_mm = URIRef(localhost_mm)
99 | RDF_mm2 = URIRef(localhost_mm2)
100 | RDF_mm3 = URIRef(localhost_mm3)
101 | # --------------------
102 | RDF_python = Literal('Python')
103 | RDF_softwareversion = Literal('PyRadiomics_' + featureVector['diagnostics_Versions_PyRadiomics']) # version of pyradiomics
104 | RDF_ROItype = Literal(ROI) # ROI
105 | RDF_Datetime = Literal(datetime.now().strftime("%Y-%m-%d")) # run at_date_time
106 | # -------------------- feature parameters -----------------
107 | RDF_featureparameter = Literal(featureVector['diagnostics_Configuration_Settings'])
108 | # For further use, split diagnostics_Configuration_Settings, but not now
109 | # RDF_resampledPixelSpacing = Literal(featureVector['diagnostics_Configuration_Settings']['resampledPixelSpacing'])
110 | # RDF_interpolator = Literal(featureVector['diagnostics_Configuration_Settings']['interpolator'])
111 | # RDF_resegmentRange = Literal(featureVector['diagnostics_Configuration_Settings']['resegmentRange'])
112 |
113 | #----------------------------------------------------------------
114 | # Load Radiomics Ontology table
115 | df_RO = pd.read_csv(os.path.join(os.getcwd(),'RadiomicsOntology','ORAW_RO_Table.csv'))
116 | #extract feature keys and values from featureVector cumputed by pyradiomcis
117 | f_key = list(featureVector.keys())
118 | f_value = list(featureVector.values())
119 |
120 | # # remove columns with general info from pyradiomics results
121 | f_index = []
122 | for i in range(len(f_key)):
123 | if 'diagnostics' not in f_key[i]: # filter out 'general_info' from featureVector
124 | f_index.append(i)
125 | radiomics_key = []
126 | radiomics_value = []
127 | for j in f_index:
128 | radiomics_key.append(f_key[j])
129 | radiomics_value.append(f_value[j])
130 |
131 | # # Adding elements to graph
132 | for i in range(len(radiomics_key)-3): # -3 means filter out patientid, RTid, and countour
133 |
134 | # # ---------------------- do text match ------------
135 | if 'log' in radiomics_key[i]:
136 | radiomics_feature = radiomics_key[i][20:]
137 | radiomics_imagetype = radiomics_key[i][0:19]
138 | radiomics_binwidth = featureVector['diagnostics_Configuration_EnabledImageTypes']['LoG']['binWidth']
139 | elif 'wavelet' in radiomics_key[i]:
140 | radiomics_feature = radiomics_key[i][12:]
141 | radiomics_imagetype = radiomics_key[i][0:11]
142 | radiomics_binwidth = featureVector['diagnostics_Configuration_EnabledImageTypes']['Wavelet']['binWidth']
143 | else:
144 | radiomics_feature = radiomics_key[i][9:]
145 | radiomics_imagetype = radiomics_key[i][0:8]
146 | radiomics_binwidth = featureVector['diagnostics_Configuration_EnabledImageTypes']['Original']['binWidth']
147 | ## --------------------------------------------------
148 | ind = pd.Index(df_RO.iloc[:,0]).get_loc(radiomics_feature)
149 | tmp_uri = URIRef(df_RO.iloc[:,1][ind])
150 | tmp_value = Literal(radiomics_value[i])
151 | #---------------------------------RDF entity for feature
152 | RDF_feature = URIRef(localhost_feature + myStructUID + '_' + urllib.quote(ROI) + '_' + radiomics_key[i])
153 | RDF_imagetype = Literal(radiomics_imagetype)
154 | RDF_binwidth = Literal('binwidth: ' + str(radiomics_binwidth))
155 | RDF_featureparameterspace = URIRef(featureparameterspace_uri + '_' + radiomics_key[i])
156 | # ----------------------------------------------------
157 | # start adding
158 | # ------------ patient layer ---------------
159 | graph.add((RDF_patid,RDF.type,patient_uri))
160 | graph.add((RDF_patid,has_pacs_study,RDF_scan))
161 | # ------------ scan layer ---------------
162 | graph.add((RDF_scan,RDF.type,scan_uri))
163 | graph.add((RDF_scan,converted_to,RDF_imagevolume))
164 | # ------------ image volume layer ---------------
165 | graph.add((RDF_imagevolume,RDF.type,image_volume_uri))
166 | graph.add((RDF_imagevolume,is_part_of,RDF_imagespace))
167 | # ------------ image space layer ---------------
168 | graph.add((RDF_imagespace,RDF.type,image_space_uri))
169 | graph.add((RDF_imagespace,used_to_compute,RDF_feature))
170 | graph.add((RDF_ROI,is_part_of,RDF_imagespace))
171 | # graph.add((RDF_ROItype,is_label_of,RDF_ROI))
172 | graph.add((RDF_ROI,has_label,RDF_ROItype))
173 | # ------------ feature layer ---------------
174 | graph.add((RDF_feature,RDF.type,tmp_uri))
175 | graph.add((RDF_feature,has_value,tmp_value))
176 |
177 | # ------------ calculatin run layer ------------
178 | graph.add((RDF_feature,computed_using,calculationrun_space_uri))
179 | graph.add((calculationrun_space_uri,performed_by,softwareproperties_uri))
180 | ### missing ontology of at_date_time -------------
181 | graph.add((calculationrun_space_uri,at_date_time,RDF_Datetime))
182 | # graph.add((datetime_uri,has_value,Literal(str(datetime.now()))))
183 | graph.add((softwareproperties_uri,has_programming_language,RDF_python))
184 | graph.add((softwareproperties_uri,has_version,RDF_softwareversion))
185 |
186 | # ------------feature parameter layer----------
187 | graph.add((RDF_feature,computed_using,RDF_featureparameterspace))
188 | graph.add((RDF_featureparameterspace,defined_by,RDF_featureparameter))
189 | graph.add((RDF_featureparameterspace,defined_by,RDF_imagetype))
190 | graph.add((RDF_featureparameterspace,defined_by,RDF_binwidth))
191 |
192 | # ----------- add unit to feature, if it has ------------------
193 | if radiomics_key[i] == 'original_shape_Volume':
194 | graph.add((RDF_feature,has_unit,RDF_mm3))
195 | graph.add((RDF_mm3,RDF.type,mm3_uri))
196 | if radiomics_key[i] == 'original_shape_SurfaceArea':
197 | graph.add((RDF_feature,has_unit,RDF_mm2))
198 | graph.add((RDF_mm2,RDF.type,mm2_uri))
199 | if radiomics_key[i] == 'original_shape_LeastAxis':
200 | graph.add((RDF_feature,has_unit,RDF_mm))
201 | graph.add((RDF_mm,RDF.type,mm_uri))
202 | if radiomics_key[i] == 'original_shape_MajorAxis':
203 | graph.add((RDF_feature,has_unit,RDF_mm))
204 | graph.add((RDF_mm,RDF.type,mm_uri))
205 | if radiomics_key[i] == 'original_shape_Maximum2DDiameterColumn':
206 | graph.add((RDF_feature,has_unit,RDF_mm))
207 | graph.add((RDF_mm,RDF.type,mm_uri))
208 | if radiomics_key[i] == 'original_shape_Maximum2DDiameterRow':
209 | graph.add((RDF_feature,has_unit,RDF_mm))
210 | graph.add((RDF_mm,RDF.type,mm_uri))
211 | if radiomics_key[i] == 'original_shape_Maximum2DDiameterSlice':
212 | graph.add((RDF_feature,has_unit,RDF_mm))
213 | graph.add((RDF_mm,RDF.type,mm_uri))
214 | if radiomics_key[i] == 'original_shape_Maximum3DDiameter':
215 | graph.add((RDF_feature,has_unit,RDF_mm))
216 | graph.add((RDF_mm,RDF.type,mm_uri))
217 | if radiomics_key[i] == 'original_shape_MinorAxis':
218 | graph.add((RDF_feature,has_unit,RDF_mm))
219 | graph.add((RDF_mm,RDF.type,mm_uri))
220 | return graph
--------------------------------------------------------------------------------