├── .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 --------------------------------------------------------------------------------