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

RuntimeError when Input a Gray-Scale Image in RadiomicsGLCM - GLCM #447

Closed Elwyslan closed 5 years ago

Elwyslan commented 5 years ago

Hello fellow devs, I have a Issue.

My code, inputing a Gray-Scale Image + Binary Mask with sizes (450x450) on "radiomics.glcm.Radiomics GLCM":

#'mask' and 'image' previously loaded...
rad = radiomics.glcm.RadiomicsGLCM(image, mask)
print(f'Type image: {type(image)}')
print(f'Size image: {image.GetSize()}')
print(f'Type mask: {type(mask)}')
print(f'Size mask: {mask.GetSize()}')
rad.execute()  #This Line Throw a RunTimeErro Exception
#......

and this happens...

Type image: <class 'SimpleITK.SimpleITK.Image'>
Size image: (450, 450)
Type mask: <class 'SimpleITK.SimpleITK.Image'>
Size mask: (450, 450)
... STACK TRACE ...
........ line 133, in _calculateMatrix self.settings.get('force2Ddimension', 0))
RuntimeError: Expected a 3D array for image and mask.

Looking at line 133, in "pyradiomics/radiomics/glcm.py", in function "def _calculateMatrix" I see the following:

"""
Compute GLCMs for the input image for every direction in 3D.
Calculated GLCMs are placed in array P_glcm with shape (i/j, a)
i/j = total gray-level bins for image array,
a = directions in 3D (generated by imageoperations.generateAngles)
"""
...........
P_glcm, angles = cMatrices.calculate_glcm(self.matrix,
                             self.maskArray,
                             numpy.array(self.settings.get('distances', [1])),
                             Ng,
                             self.settings.get('force2D', False),
                             self.settings.get('force2Ddimension', 0))

From what I understand, this line of code forces the input image to be 3D(450x450x3). So, when I input a 2D image(450x450) I got a RuntimeError.

This is True because when I input a RGB image + Mask with sizes (450x450x3) I got no errors.

So, "radiomics.glcm.RadiomicsGLCM" is avaliable only for 3D images? Or there are a way to use in Gray-Scale Images?

Thanks in advance.

Elwyslan commented 5 years ago

I'm looking at the source code to see the source of the exception...

Looking for these two params, force2D and force2Dimensions, I found in "radiomics/schemas/paramSchema.yaml"

force2D:
    type: bool
force2Ddimension:
    type: int
    range:
    min: 0
    max: 2

Then, if If I instantiate like this:

rad = radiomics.glcm.RadiomicsGLCM(image, mask, force2D=True, force2Ddimension=1)

The 'settings dictionary' will contain {'force2D':True, 'force2Ddimension':1}

But still the error the RuntimeError persists.

Looking more deep I found in "/radiomics/src/_cmatrices.c" the function "static PyObject cmatrices_calculate_glcm(PyObject self, PyObject *args)"

This function do the following steps:

// 1 - Parse the input tuple
// 2 - Interpret the image and mask objects as numpy arrays
// 3 - Check if array input is valid and extract sizes and strides of image and mask
// 4 - Interpret the distance object as numpy array
// 5 - Get the number of distances and the distances array data
//.....

In Step 3, the code calls check_arrays(image_arr, mask_arr, size, strides)

Which contains:

int check_arrays(PyArrayObject *image_arr, PyArrayObject *mask_arr, int *size, int *strides)
{
   if (!image_arr || !mask_arr)
   {
        Py_XDECREF(image_arr);
        Py_XDECREF(mask_arr);
        PyErr_SetString(PyExc_RuntimeError, "Error parsing array arguments.");
        return 1;
    }
// Check if Image and Mask have 3 dimensions, and if Angles has 2 dimensions
    if (PyArray_NDIM(image_arr) != 3 || PyArray_NDIM(mask_arr) != 3)
    {
        Py_XDECREF(image_arr);
        Py_XDECREF(mask_arr);
        //THIS LINE THROW MY EXCEPTION
        PyErr_SetString(PyExc_RuntimeError, "Expected a 3D array for image and mask.");
        return 2;
  }
....
}

And I think there's a bug right there. force2Ddimension and force2D are not checked before the dimensions of the image been checked.

As conclusion i guess, no matter what the value which 'force2Ddimension' and 'force2D' assume, every time that a 2D array came as input, there will be a Runtimerror.

JoostJM commented 5 years ago

@Elwyslan that is correct, this is not a bug.

Please see this question in the FAQ section of PyRadiomics. This topic also points to this thread on the Pyradiomics Google Groups.

In short: 1) PyRadiomics only supports grayscale images 2) Input is required to have 3 dimensions, even if input is 2D (in which case, 1 dimension has size 1)

force2D is a setting that allows you to compute texture features using only in-plane neighbors, even if the volume is 3D. This relaxes the requirement of voxel isotropy from fully isotropic voxels to only isotropic in-plane (thereby allowing for different slice thickness, which is common in radiologic imaging).

Elwyslan commented 5 years ago

Thank you. The 4th answer in [https://groups.google.com/forum/#!topic/pyradiomics/QLdD_qEw3PY] which you gave the trick "You can use SimpleITK.JoinSeries to create these "3D" images from 2D" solves my problem.