AIM-Harvard / pyradiomics

Open-source python package for the extraction of Radiomics features from 2D and 3D images and binary masks. Support: https://discourse.slicer.org/c/community/radiomics
http://pyradiomics.readthedocs.io/
BSD 3-Clause "New" or "Revised" License
1.14k stars 493 forks source link

Voxelwise featuremap misalignment #472

Closed nwschurink closed 5 years ago

nwschurink commented 5 years ago

When performing voxelwise feature extraction, the resulting featuremap has an offset compared to the original input mask.

I've used following parameter file

imageType:
  Original: {}

featureClass:
  firstorder: ['entropy']

setting:
  binWidth: 0.1
  force2D: false
  label: 1
  normalize: true
  verbose: true

voxelSetting:
  kernelRadius: 3
  maskedKernel: False
  initValue: nan
  voxelBatch: 10000

and following code

extractor = featureextractor.RadiomicsFeaturesExtractor(parameters)
result = extractor.execute(T2_image,T2_mask,voxelBased=True)
sitk.WriteImage(result['original_firstorder_Entropy'],'entropy_map.nii.gz')

The result looks like a correct feature map, however when loading in 3DSlicer the feature map and original mask don't line up

image image image

direction cosines and pixelspacing are the same (up to a very small offset)

In[8]: result.GetDirection()
Out[8]: 
(0.9998692168806838,
 -0.0017433978040942717,
 0.01607823644291258,
 -0.002462451867338953,
 -0.998992429138316,
 0.04481141261257783,
 -0.015983912700255,
 0.04484514565059839,
 0.9988660709086836)

In[9]: T2_mask.GetDirection()
Out[9]: 
(0.9998692168806839,
 -0.0017433978040942736,
 0.01607823644291257,
 -0.002462451867338953,
 -0.9989924291383161,
 0.04481141261257781,
 -0.01598391270025502,
 0.04484514565059839,
 0.9988660709086835)

In [12]: foo.GetSpacing()
Out[12]: (0.78125, 0.78125, 5.000000953674316)

In [13]: T2_mask.GetSpacing()
Out[13]: (0.7812500000000004, 0.7812499999999999, 5.0000009536743235)

I figured out the exact translation between the output feature map and input mask and this is [0.78125, -0.78125, 5.00], which shows that the mask is exactly 1 pixel off in each direction.

image

image

Maybe a small indexing/slicing error somewhere?

nwschurink commented 5 years ago

I've also figured out that the YAML file doesnt get read correctly. In the results I find the following keys, which are clearly set differently than in the YAML file.

('diagnostics_Versions_PyRadiomics', '2.1.2.post45+g49bda8c'), ('diagnostics_Versions_Numpy', '1.15.0'), ('diagnostics_Versions_SimpleITK', '1.1.0.dev362-gb3783'), ('diagnostics_Versions_PyWavelet', '0.5.2'), ('diagnostics_Versions_Python', '3.6.6'), ('diagnostics_Configuration_Settings', {'minimumROIDimensions': 2, 'minimumROISize': None, 'normalize': False, 'normalizeScale': 1, 'removeOutliers': None, 'resampledPixelSpacing': None, 'interpolator': 'sitkBSpline', 'preCrop': False, 'padDistance': 5, 'distances': [1], 'force2D': False, 'force2Ddimension': 0, 'resegmentRange': None, 'label': 1, 'additionalInfo': True, 'voxelBased': True}), ('diagnostics_Configuration_EnabledImageTypes', {'Original': {}}), ('diagnostics_Image-original_Hash', '581ba1c8b6bd9d5d4c7ac530624db41f9cd297bf'), ('diagnostics_Image-original_Dimensionality', '3D'), ('diagnostics_Image-original_Spacing', (0.78125, 0.78125, 5.000000953674316)), ('diagnostics_Image-original_Size', (256, 256, 35)), ('diagnostics_Image-original_Mean', 718.5894209263339), ('diagnostics_Image-original_Minimum', 0.0), ('diagnostics_Image-original_Maximum', 3651.7451171875), ('diagnostics_Mask-original_Hash', 'c3d9e3436c8e7533c69b8393f0bd7c4d6075b9bc'), ('diagnostics_Mask-original_Spacing', (0.7812500000000004, 0.7812499999999999, 5.0000009536743235)), ('diagnostics_Mask-original_Size', (256, 256, 35)), ('diagnostics_Mask-original_BoundingBox', (97, 58, 11, 59, 56, 10)), ('diagnostics_Mask-original_VoxelNum', 9722), ('diagnostics_Mask-original_VolumeNum', 1), ('diagnostics_Mask-original_CenterOfMassIndex', (129.26321744497017, 89.07179592676404, 15.926969759308784)), ('diagnostics_Mask-original_CenterOfMass', (-0.9770436125988056, 76.41675422945121, -5.951556744714452))

When setting them explicitely using:

    params = {}
    params['binWidth']= 0.1
    params['force2D'] = False
    params['label'] = 1
    params['normalize'] = True
    params['verbose'] = True
    params['kernelRadius'] = 3
    params['maskedKernel'] = False
    params['initValue'] = nan
    params['voxelBatch'] = 10000

    extractor = featureextractor.RadiomicsFeaturesExtractor(**params)
    extractor.disableAllFeatures()
    extractor.enableFeaturesByName(firstorder=['Entropy'])
    result = extractor.execute(T2_image,T2_mask,voxelBased=True)

I get the following error:

Traceback (most recent call last):

  File "<ipython-input-75-367fe56429e8>", line 19, in <module>
    result = extractor.execute(T2_image,T2_mask,voxelBased=True)

  File "D:\Anaconda3\lib\site-packages\pyradiomics-2.1.2.post45+g49bda8c-py3.6-win-amd64.egg\radiomics\featureextractor.py", line 477, in execute
    featureVector.update(self.computeFeatures(inputImage, inputMask, imageTypeName, **inputKwargs))

  File "D:\Anaconda3\lib\site-packages\pyradiomics-2.1.2.post45+g49bda8c-py3.6-win-amd64.egg\radiomics\featureextractor.py", line 570, in computeFeatures
    featureClass = self.featureClasses[featureClassName](image, mask, **kwargs)

  File "D:\Anaconda3\lib\site-packages\pyradiomics-2.1.2.post45+g49bda8c-py3.6-win-amd64.egg\radiomics\firstorder.py", line 33, in __init__
    super(RadiomicsFirstOrder, self).__init__(inputImage, inputMask, **kwargs)

  File "D:\Anaconda3\lib\site-packages\pyradiomics-2.1.2.post45+g49bda8c-py3.6-win-amd64.egg\radiomics\base.py", line 87, in __init__
    self._initVoxelBasedCalculation()

  File "D:\Anaconda3\lib\site-packages\pyradiomics-2.1.2.post45+g49bda8c-py3.6-win-amd64.egg\radiomics\firstorder.py", line 63, in _initVoxelBasedCalculation
    self.imageArray[~self.maskArray] = numpy.nan

TypeError: ufunc 'invert' not supported for the input types, and the inputs could not be safely coerced to any supported types according to the casting rule ''safe''

However, manually inverting the input mask doesn't lead to this error. So I assume this has to do with some kind of type conversion within pyradiomics.

JoostJM commented 5 years ago

@nwschurink The first error is indeed a misalignment error, this is specific for firstorder features and has been fixed in 64a3ab0