diff --git a/bin/Params.yaml b/bin/Params.yaml index 1cb7bbc7..f511eb78 100644 --- a/bin/Params.yaml +++ b/bin/Params.yaml @@ -33,8 +33,19 @@ inputImage: # Featureclasses, from which features must be calculated. If a featureclass is not mentioned, no features are calculated # for that class. Otherwise, the specified features are calculated, or, if none are specified, all are calculated. featureClass: - shape: # for lists none values are allowed, in this case, all features are enabled + shape: ['Volume', + 'SurfaceArea', + 'SurfaceVolumeRatio', + 'Sphericity', + 'SphericalDisproportion', + 'Maximum3DDiameter', + 'Maximum2DDiameterSlice', + 'Maximum2DDiameterColumn', + 'Maximum2DDiameterRow', + 'Elongation', + 'Flatness', + 'Roundness'] # Only enable these shape descriptors (disables redundant Compactness 1 and Compactness 2) firstorder: [] # specifying an empty list has the same effect as specifying nothing. - glcm: + glcm: # for lists none values are allowed, in this case, all features are enabled glrlm: glszm: diff --git a/data/baseline/baseline_shape.csv b/data/baseline/baseline_shape.csv index 4701e032..6ec9c173 100644 --- a/data/baseline/baseline_shape.csv +++ b/data/baseline/baseline_shape.csv @@ -1,6 +1,6 @@ -Patient ID,general_info_BoundingBox,general_info_GeneralSettings,general_info_ImageHash,general_info_ImageSpacing,general_info_InputImages,general_info_MaskHash,general_info_Version,general_info_VolumeNum,general_info_VoxelNum,Maximum3DDiameter,Compactness2,Maximum2DDiameterSlice,Sphericity,Compactness1,Elongation,SurfaceVolumeRatio,Volume,Flatness,SphericalDisproportion,Roundness,SurfaceArea,Maximum2DDiameterColumn,Maximum2DDiameterRow -brain1,(162; 84; 11; 47; 70; 7),{'verbose': False; 'voxelArrayShift': 2000; 'binWidth': 25; 'label': 1; 'interpolator': None; 'symmetricalGLCM': False; 'resampledPixelSpacing': None; 'gldm_a': 0; 'weightingNorm': None; 'padDistance': 5},5c9ce3ca174f0f8324aa4d277e0fef82dc5ac566,(0.7812499999999999; 0.7812499999999999; 6.499999999999998),{'original': {}},9dc2c3137b31fd872997d92c9a92d5178126d9d3,0.post403.dev0+gd611f07,2,4137,65.53661458728622,0.11412770190085314,47.218791363317415,0.48506174422170256,26.754678721506981,1.7789885567018646,0.39230826186319256,16412.65869140624,1.2191850589688844,2.0615932134671486,0.6146906661500379,6438.8216037794018,44.548790405155771,61.580176713472483 -brain2,(205; 155; 8; 20; 15; 3),{'verbose': False; 'voxelArrayShift': 2000; 'binWidth': 25; 'label': 1; 'interpolator': None; 'symmetricalGLCM': False; 'resampledPixelSpacing': None; 'gldm_a': 0; 'weightingNorm': None; 'padDistance': 5},f2b8fbc4d5d1da08a1a70e79a301f8a830139438,(0.7812499999999999; 0.7812499999999999; 6.499999999999998),{'original': {}},b41049c71633e194bee4891750392b72eabd8800,0.post403.dev0+gd611f07,1,453,19.654138400092737,0.45448842446195314,15.566295972790057,0.76884880229711317,10.643947668070684,1.3499482848728939,0.51734387202502263,1797.1801757812489,1.1970744957871078,1.3006458448166522,1.0348881179911384,929.76015086528207,18.584714191036131,14.779664291265206 -breast1,(21; 64; 8; 9; 12; 3),{'verbose': False; 'voxelArrayShift': 2000; 'binWidth': 25; 'label': 1; 'interpolator': None; 'symmetricalGLCM': False; 'resampledPixelSpacing': None; 'gldm_a': 0; 'weightingNorm': None; 'padDistance': 5},016951a8f9e8e5de21092d9d62b77262f92e04a5,(0.664062; 0.664062; 2.1),{'original': {}},5aa7d57fd57e83125b605c036c40f4a0d0cfd3e4,0.post403.dev0+gd611f07,1,143,7.772702166226878,0.38380439201415423,7.7726464321122961,0.72672479835852832,2.407442715594033,1.4286044695895188,1.3055392937755954,132.4257954551532,1.0230321043228265,1.3760367091624301,0.8556418393749154,172.88707947619218,5.3539199187231041,7.3046820000000006 -lung1,(206; 347; 32; 24; 26; 3),{'verbose': False; 'voxelArrayShift': 2000; 'binWidth': 25; 'label': 1; 'interpolator': None; 'symmetricalGLCM': False; 'resampledPixelSpacing': None; 'gldm_a': 0; 'weightingNorm': None; 'padDistance': 5},34dca4200809a5e76c702d6b9503d958093057a3,(0.5703125; 0.5703125; 5.0),{'original': {}},054d887740012177bd1f9031ddac2b67170af0f3,0.post403.dev0+gd611f07,1,837,18.182594712754316,0.43779658729410265,15.978930906357792,0.7593187496870597,9.0459132938754774,1.3912249269789807,0.57467140294582819,1361.1978149414062,1.3975132116994384,1.3169699818582548,1.031094246578549,782.2414579991738,16.044440540748841,13.537563480922259 -lung2,(318; 333; 15; 87; 66; 11),{'verbose': False; 'voxelArrayShift': 2000; 'binWidth': 25; 'label': 1; 'interpolator': None; 'symmetricalGLCM': False; 'resampledPixelSpacing': None; 'gldm_a': 0; 'weightingNorm': None; 'padDistance': 5},14f57fd04838eb8c9cca2a0dd871d29971585975,(0.6269531; 0.6269531; 5.0),{'original': {}},e284ff05593bc6cb2747261882e452d4efbccb3a,0.post403.dev0+gd611f07,1,24644,65.4490741679312,0.30580544578561636,55.235955786717163,0.67372356547818002,60.760123315042613,1.3452677171012488,0.19691793910543032,48434.10876246395,1.3057448083030738,1.4842882915788209,0.8768796050137607,9537.5448799126643,61.55885055061254,57.721196463628289 +Patient ID,general_info_BoundingBox,general_info_GeneralSettings,general_info_ImageHash,general_info_ImageSpacing,general_info_InputImages,general_info_MaskHash,general_info_Version,general_info_VolumeNum,general_info_VoxelNum,Maximum3DDiameter,Compactness2,Maximum2DDiameterSlice,Sphericity,Compactness1,Elongation,SurfaceVolumeRatio,Volume,Flatness,SphericalDisproportion,SurfaceArea,Maximum2DDiameterColumn,Maximum2DDiameterRow +brain1,(162; 84; 11; 47; 70; 7),{'verbose': False; 'voxelArrayShift': 2000; 'enableCExtensions': True; 'interpolator': None; 'resampledPixelSpacing': None; 'gldm_a': 0; 'weightingNorm': None; 'normalizeScale': 1; 'normalize': False; 'additionalInfo': True; 'removeOutliers': None; 'binWidth': 25; 'label': 1; 'symmetricalGLCM': False; 'padDistance': 5},5c9ce3ca174f0f8324aa4d277e0fef82dc5ac566,(0.7812499999999999; 0.7812499999999999; 6.499999999999998),{'original': {}},9dc2c3137b31fd872997d92c9a92d5178126d9d3,0.post403.dev0+gd611f07,2,4137,65.53661458728622,0.11412770190085314,47.218791363317415,0.48506174422170256,0.017922327666112708,1.7789885567018646,0.39230826186319256,16412.65869140624,1.2191850589688844,2.0615932134671486,6438.8216037794018,44.548790405155771,61.580176713472483 +brain2,(205; 155; 8; 20; 15; 3),{'verbose': False; 'voxelArrayShift': 2000; 'enableCExtensions': True; 'interpolator': None; 'resampledPixelSpacing': None; 'gldm_a': 0; 'weightingNorm': None; 'normalizeScale': 1; 'normalize': False; 'additionalInfo': True; 'removeOutliers': None; 'binWidth': 25; 'label': 1; 'symmetricalGLCM': False; 'padDistance': 5},f2b8fbc4d5d1da08a1a70e79a301f8a830139438,(0.7812499999999999; 0.7812499999999999; 6.499999999999998),{'original': {}},b41049c71633e194bee4891750392b72eabd8800,0.post403.dev0+gd611f07,1,453,19.654138400092737,0.45448842446195314,15.566295972790057,0.76884880229711317,0.035765169710140834,1.3499482848728939,0.51734387202502263,1797.1801757812489,1.1970744957871078,1.3006458448166522,929.76015086528207,18.584714191036131,14.779664291265206 +breast1,(21; 64; 8; 9; 12; 3),{'verbose': False; 'voxelArrayShift': 2000; 'enableCExtensions': True; 'interpolator': None; 'resampledPixelSpacing': None; 'gldm_a': 0; 'weightingNorm': None; 'normalizeScale': 1; 'normalize': False; 'additionalInfo': True; 'removeOutliers': None; 'binWidth': 25; 'label': 1; 'symmetricalGLCM': False; 'padDistance': 5},016951a8f9e8e5de21092d9d62b77262f92e04a5,(0.664062; 0.664062; 2.1),{'original': {}},5aa7d57fd57e83125b605c036c40f4a0d0cfd3e4,0.post403.dev0+gd611f07,1,143,7.772702166226878,0.38380439201415423,7.7726464321122961,0.72672479835852832,0.032866529447821202,1.4286044695895188,1.3055392937755954,132.4257954551532,1.0230321043228265,1.3760367091624301,172.88707947619218,5.3539199187231041,7.3046820000000006 +lung1,(206; 347; 32; 24; 26; 3),{'verbose': False; 'voxelArrayShift': 2000; 'enableCExtensions': True; 'interpolator': None; 'resampledPixelSpacing': None; 'gldm_a': 0; 'weightingNorm': None; 'normalizeScale': 1; 'normalize': False; 'additionalInfo': True; 'removeOutliers': None; 'binWidth': 25; 'label': 1; 'symmetricalGLCM': False; 'padDistance': 5},34dca4200809a5e76c702d6b9503d958093057a3,(0.5703125; 0.5703125; 5.0),{'original': {}},054d887740012177bd1f9031ddac2b67170af0f3,0.post403.dev0+gd611f07,1,837,18.182594712754316,0.43779658729410265,15.978930906357792,0.7593187496870597,0.035102258719353144,1.3912249269789807,0.57467140294582819,1361.1978149414062,1.3975132116994384,1.3169699818582548,782.2414579991738,16.044440540748841,13.537563480922259 +lung2,(318; 333; 15; 87; 66; 11),{'verbose': False; 'voxelArrayShift': 2000; 'enableCExtensions': True; 'interpolator': None; 'resampledPixelSpacing': None; 'gldm_a': 0; 'weightingNorm': None; 'normalizeScale': 1; 'normalize': False; 'additionalInfo': True; 'removeOutliers': None; 'binWidth': 25; 'label': 1; 'symmetricalGLCM': False; 'padDistance': 5},14f57fd04838eb8c9cca2a0dd871d29971585975,(0.6269531; 0.6269531; 5.0),{'original': {}},e284ff05593bc6cb2747261882e452d4efbccb3a,0.post403.dev0+gd611f07,1,24644,65.4490741679312,0.30580544578561636,55.235955786717163,0.67372356547818002,0.029337390690641122,1.3452677171012488,0.19691793910543032,48434.10876246395,1.3057448083030738,1.4842882915788209,9537.5448799126643,61.55885055061254,57.721196463628289 diff --git a/radiomics/shape.py b/radiomics/shape.py index 77cd6419..dfcb80a6 100644 --- a/radiomics/shape.py +++ b/radiomics/shape.py @@ -8,15 +8,14 @@ class RadiomicsShape(base.RadiomicsFeaturesBase): r""" In this group of features we included descriptors of the three-dimensional size and shape of the tumor region. - Let in the following definitions denote :math:`V` the volume and :math:`A` the surface area of the volume of interest. + Let in the following definitions denote :math:`V` the volume (mm\ :sup:`3`) and :math:`A` the surface area of the + volume (mm\ :sup:`2`) of interest. """ def __init__(self, inputImage, inputMask, **kwargs): super(RadiomicsShape, self).__init__(inputImage, inputMask, **kwargs) self.pixelSpacing = numpy.array(inputImage.GetSpacing()[::-1]) - z, x, y = self.pixelSpacing - self.cubicMMPerVoxel = z * x * y # Use SimpleITK for some shape features self.lssif = sitk.LabelShapeStatisticsImageFilter() @@ -163,13 +162,13 @@ def getVolumeFeatureValue(self): def getSurfaceAreaFeatureValue(self): r""" - Calculate the surface area of the tumor region in square millimeters. + Calculate the surface area of the tumor region in square millimeters using a marching cubes algorithm. :math:`A = \displaystyle\sum^{N}_{i=1}{\frac{1}{2}|\textbf{a}_i\textbf{b}_i \times \textbf{a}_i\textbf{c}_i|}` Where: - :math:`N` is the number of triangles forming the surface of the volume + :math:`N` is the number of triangles forming the surface mesh of the volume (ROI) :math:`a_ib_i` and :math:`a_ic_i` are the edges of the :math:`i`\ :sup:`th` triangle formed by points :math:`a_i`, :math:`b_i` and :math:`c_i` @@ -182,36 +181,85 @@ def getSurfaceVolumeRatioFeatureValue(self): Calculate the surface area to volume ratio of the tumor region :math:`surface\ to\ volume\ ratio = \frac{A}{V}` + + Here, a lower value indicates a more compact (sphere-like) shape. """ return (self.SurfaceArea / self.Volume) + def getSphericityFeatureValue(self): + r""" + Calculate the Sphericity of the tumor region. + + :math:`sphericity = \frac{\sqrt[3]{36 \pi V^2}}{A}` + + Sphericity is a measure of the roundness of the shape of the tumor region relative to a sphere. It is a + dimensionless measure, independent of scale and orientation. The value range is :math:`0 < sphericity \leq 1`, where + a value of 1 indicates a perfect sphere (a sphere has the smallest possible surface area for a given volume, + compared to other solids). + + **N.B. This feature is correlated to Compactness 1, Compactness 2 and Spherical Disproportion. In the default + parameter file provided in the** ``pyradiomics\\bin`` **folder, only Compactness 1 and Compactness 2 are therefore + disabled.** + """ + return (36 * numpy.pi * self.Volume ** 2) ** (1.0 / 3.0) / self.SurfaceArea + def getCompactness1FeatureValue(self): r""" Calculate the compactness (1) of the tumor region. - :math:`compactness\ 1 = \frac{V}{\sqrt{\pi}A^{\frac{2}{3}}}` + :math:`compactness\ 1 = \frac{V}{\sqrt{\pi A^3}}` + + Similar to Sphericity, Compactness 1 is a measure of how compact the shape of the tumor is relative to a sphere + (most compact). It is therefore correlated to Sphericity and redundant. It is provided here for completeness. + The value range is :math:`0 < compactness\ 1 \leq \frac{1}{6 \pi}`, where a value of :math:`\frac{1}{6 \pi}` + indicates a perfect sphere. + + By definition, :math:`compactness\ 1 = \frac{1}{6 \pi}\sqrt{compactness\ 2} = + \frac{1}{6 \pi}\sqrt{sphericity^3}`. - Compactness 1 is a measure of how compact the shape of the tumor is - relative to a sphere (most compact). It is a dimensionless measure, - independent of scale and orientation. Compactness 1 is defined as the - ratio of volume to the :math:`\sqrt{\text{surface area}^3}`. This is a measure of the - compactness of the shape of the image ROI + **N.B. This feature is correlated to Compactness 2, Sphericity and Spherical Disproportion. In the default + parameter file provided in the** ``pyradiomics\\bin`` **folder, only Compactness 1 and Compactness 2 are therefore + disabled.** """ - return ((self.Volume) / ((self.SurfaceArea) ** (2.0 / 3.0) * numpy.sqrt(numpy.pi))) + return ((self.Volume) / ((self.SurfaceArea) ** (3.0 / 2.0) * numpy.sqrt(numpy.pi))) def getCompactness2FeatureValue(self): r""" Calculate the Compactness (2) of the tumor region. - :math:`compactness\ 2 = 36\pi\frac{V^2}{A^3}` + :math:`compactness\ 2 = 36 \pi \frac{V^2}{A^3}` - Compactness 2 is a measure of how compact the shape of the tumor is - relative to a sphere (most compact). It is a dimensionless measure, - independent of scale and orientation. This is a measure of the compactness - of the shape of the image ROI. + Similar to Sphericity and Compactness 1, Compactness 2 is a measure of how compact the shape of the tumor is + relative to a sphere (most compact). It is a dimensionless measure, independent of scale and orientation. The value + range is :math:`0 < compactness\ 2 \leq 1`, where a value of 1 indicates a perfect sphere. + + By definition, :math:`compactness\ 2 = (sphericity)^3` + + **N.B. This feature is correlated to Compactness 1, Sphericity and Spherical Disproportion. In the default + parameter file provided in the** ``pyradiomics\\bin`` **folder, only Compactness 1 and Compactness 2 are therefore + disabled.** """ return ((36.0 * numpy.pi) * ((self.Volume) ** 2.0) / ((self.SurfaceArea) ** 3.0)) + def getSphericalDisproportionFeatureValue(self): + r""" + Calculate the Spherical Disproportion of the tumor region. + + :math:`spherical\ disproportion = \frac{A}{4\pi R^2} = \frac{A}{\sqrt[3]{36 \pi V^2}}` + + Where :math:`R` is the radius of a sphere with the same volume as the tumor, and equal to + :math:`\sqrt[3]{\frac{3V}{4\pi}}`. + + Spherical Disproportion is the ratio of the surface area of the tumor region to the surface area of a sphere with + the same volume as the tumor region, and by definition, the inverse of Sphericity. Therefore, the value range is + :math:`spherical\ disproportion \geq 1`, with a value of 1 indicating a perfect sphere. + + **N.B. This feature is correlated to Compactness 1, Sphericity and Spherical Disproportion. In the default + parameter file provided in the** ``pyradiomics\\bin`` **folder, only Compactness 1 and Compactness 2 are therefore + disabled.** + """ + return self.SurfaceArea / (36 * numpy.pi * self.Volume ** 2) ** (1.0 / 3.0) + def getMaximum3DDiameterFeatureValue(self): r""" Calculate the largest pairwise euclidean distance between tumor surface voxels. @@ -240,32 +288,6 @@ def getMaximum2DDiameterRowFeatureValue(self): return self._getMaximum2Ddiameter(2) - def getSphericalDisproportionFeatureValue(self): - r""" - Calculate the Spherical Disproportion of the tumor region. - - :math:`spherical\ disproportion = \frac{A}{4\pi R^2}` - - Where :math:`R` is the radius of a sphere with the same volume as the tumor. - - Spherical Disproportion is the ratio of the surface area of the - tumor region to the surface area of a sphere with the same - volume as the tumor region. - """ - R = self.lssif.GetEquivalentSphericalRadius(self.label) - return ((self.SurfaceArea) / (4.0 * numpy.pi * (R ** 2.0))) - - def getSphericityFeatureValue(self): - r""" - Calculate the Sphericity of the tumor region. - - :math:`sphericity = \frac{\pi^{\frac{1}{3}}(6V)^{\frac{2}{3}}}{A}` - - Sphericity is a measure of the roundness of the shape of the tumor region - relative to a sphere. This is another measure of the compactness of a tumor. - """ - return (((numpy.pi) ** (1.0 / 3.0) * (6.0 * self.Volume) ** (2.0 / 3.0)) / (self.SurfaceArea)) - def getElongationFeatureValue(self): """ @@ -278,12 +300,6 @@ def getFlatnessFeatureValue(self): """ return self.lssif.GetFlatness(self.label) - def getRoundnessFeatureValue(self): - """ - - """ - return self.lssif.GetRoundness(self.label) - def _interpolate(self, grid, p1, p2): diff = (.5 - self.maskArray[tuple(grid[p1])]) / (self.maskArray[tuple(grid[p2])] - self.maskArray[tuple(grid[p1])]) return (grid[p1] + ((grid[p2] - grid[p1]) * diff)) * self.pixelSpacing