Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Revise Compactness 1 Formulas #218

Merged
merged 3 commits into from
Mar 14, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions bin/Params.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
12 changes: 6 additions & 6 deletions data/baseline/baseline_shape.csv
Original file line number Diff line number Diff line change
@@ -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
114 changes: 65 additions & 49 deletions radiomics/shape.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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`
Expand All @@ -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.
Expand Down Expand Up @@ -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):
"""

Expand All @@ -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
Expand Down