Skip to content

Commit

Permalink
Merge pull request #393 from JoostJM/revise-bincount-checks
Browse files Browse the repository at this point in the history
Revise bincount checks
  • Loading branch information
JoostJM committed Jul 2, 2018
2 parents c8ceee2 + 5648984 commit 69933c7
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 17 deletions.
58 changes: 58 additions & 0 deletions docs/faq.rst
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ used as input for PyRadiomics. Please note that only one file location can be pr
provide the image in DICOM format, load the DICOM images using SimpleITK functionality and pass the resultant image
object instead.

.. _radiomics_geometry_mismatch:

Geometry mismatch between image and mask
########################################

Expand Down Expand Up @@ -164,6 +166,62 @@ out, or stored in a separate log file. The output is regulated by :py:func:`radi
logger can be accessed via ``radiomics.logger``. See also :ref:`here <radiomics-logging-label>` and the examples
included in the repository on how to set up logging.

I'm unable to calculate texture matrices and getting a RunTimeError instead
###########################################################################

This error means that something went wrong during the calculation of the matrices in the C extensions.
There are several potential causes for this error:

- "Error parsing array arguments."

This error is thrown when either the Image or the Mask provided to the function could not be interpreted as a numpy array.

- "Expected a 3D array for image and mask."

Thrown when either the Image or Mask Provided did not have 3 dimensions (in case of a single slice calculation, the
input arrays should still have 3 dimensions, although one of them will then have a size of 1).

- "Dimensions of image and mask do not match."

This means that the size of the mask array does not match the size of the image array. Because numpy arrays do not
contain information on the transformation to the physical world, input arrays of differing sizes cannot be matched.
You can solve this error by resampling the SimplITK-Image object of the Mask to the geometry of the Image before
converting them to their respective numpy arrays for feature calculation. See also :ref:`radiomics_geometry_mismatch`.

- "Error parsing distances array."

This error is shown if the C extension was not able to interpret the distances argument that was provided. In the
settings, the ``distances`` parameter should be either a tuple or a list of values.

- "Expecting distances array to be 1-dimensional."

Again an error in the provided distances. The list provided should be 1 dimensional (i.e. no nested lists).

- "Error calculating angles."

This error means there was an issue in generating the angles based on the distances provided. Currently, this only
occurs when distances < 1 are provided.

- "Number of elements in <Matrix> would overflow index variable! (...)"

This error is shown when the size of the (flattened) output array would be larger than the maximum integer value
(~2 mln). This is generally caused by a too large number of bins after discretization, resulting in a too large range of
gray values in the discretized image used for texture calculation. We generally advise to chose a bin width so, that the
number of bins after discretization does not exceed 150-200. Running the code with DEBUG logging enabled shows the
number of bins that are generated and may help to give an indication as to how large your matrices are.

- "Failed to initialize output array for <Matrix>"

This means that the computer was unable to allocate memory for the output. This is most likely due to a too large output
size or too little free memory being available. Similar as above, run with DEBUG logging to see how many bins are
generated (giving an indication on how large the output matrices are).

- "Calculation of <Matrix> Failed."

This error means there was a problem in the calculation of the matrix itself. It is generally thrown if the code tries
to set an element in the output array that is out-of-range. This can happen if there are voxels inside the ROI that
have gray values that are larger than the ``Ng`` parameter that is provided when calling the C function from Python.

I'm able to extract features, but many are NaN, 0 or 1. What happened?
######################################################################

Expand Down
4 changes: 0 additions & 4 deletions radiomics/imageoperations.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,6 @@ def getBinEdges(binwidth, parameterValues):
if len(binEdges) == 1: # Flat region, ensure that there is 1 bin
binEdges = [binEdges[0] - .5, binEdges[0] + .5] # Simulates binEdges returned by numpy.histogram if bins = 1

if len(binEdges) > 7000:
raise ValueError('Too many (%i) bins calculated! Absolute maximum number of bins is 7000, '
'but ~100 bins is advised. Please increase bin width.' % len(binEdges))

logger.debug('Calculated %d bins for bin width %g with edges: %s)', len(binEdges) - 1, binwidth, binEdges)

return binEdges # numpy.histogram(parameterValues, bins=binedges)
Expand Down
66 changes: 53 additions & 13 deletions radiomics/src/_cmatrices.c
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ moduleinit(void)
module_methods, module_docstring);
#endif

if (m == NULL)
if (!m)
return NULL;

return m;
Expand Down Expand Up @@ -127,7 +127,7 @@ static PyObject *cmatrices_calculate_glcm(PyObject *self, PyObject *args)
// Interpret the distance object as numpy array
distances_arr = (PyArrayObject *)PyArray_FROM_OTF(distances_obj, NPY_INT, NPY_ARRAY_FORCECAST | NPY_ARRAY_UPDATEIFCOPY | NPY_ARRAY_IN_ARRAY);

if (distances_arr == NULL)
if (!distances_arr)
{
Py_XDECREF(image_arr);
Py_XDECREF(mask_arr);
Expand Down Expand Up @@ -170,7 +170,7 @@ static PyObject *cmatrices_calculate_glcm(PyObject *self, PyObject *args)
dims[2] = n_a;

// Check that the maximum size of the array won't overflow the index variable (int32)
if (dims[0] * dims[1] * dims[2] > 2147483648)
if (dims[0] * dims[1] * dims[2] > INT_MAX)
{
Py_XDECREF(image_arr);
Py_XDECREF(mask_arr);
Expand All @@ -180,6 +180,14 @@ static PyObject *cmatrices_calculate_glcm(PyObject *self, PyObject *args)
}

glcm_arr = (PyArrayObject *)PyArray_SimpleNew(3, dims, NPY_DOUBLE);
if (!glcm_arr)
{
Py_XDECREF(image_arr);
Py_XDECREF(mask_arr);
free(angles);
PyErr_SetString(PyExc_RuntimeError, "Failed to initialize output array for GLCM");
return NULL;
}

// Get arrays in Ctype
image = (int *)PyArray_DATA(image_arr);
Expand Down Expand Up @@ -262,12 +270,12 @@ static PyObject *cmatrices_calculate_glszm(PyObject *self, PyObject *args)
// add +1 to the size so in the case every voxel represents a separate region,
// tempData still contains a -1 element at the end
tempData = (int *)calloc((2 * Ns) + 1, sizeof(int));
if (tempData == NULL) // No memory allocated
if (!tempData) // No memory allocated
{
Py_XDECREF(image_arr);
Py_XDECREF(mask_arr);
free(angles);
PyErr_SetString(PyExc_RuntimeError, "Failed to allocate memory for tempData");
PyErr_SetString(PyExc_RuntimeError, "Failed to allocate memory for tempData (GLSZM)");
return NULL;
}

Expand Down Expand Up @@ -298,7 +306,7 @@ static PyObject *cmatrices_calculate_glszm(PyObject *self, PyObject *args)
dims[1] = maxRegion;

// Check that the maximum size of the array won't overflow the index variable (int32)
if (dims[0] * dims[1] > 2147483648)
if (dims[0] * dims[1] > INT_MAX)
{
free(tempData);
free(angles);
Expand All @@ -307,6 +315,14 @@ static PyObject *cmatrices_calculate_glszm(PyObject *self, PyObject *args)
}

glszm_arr = (PyArrayObject *)PyArray_SimpleNew(2, dims, NPY_DOUBLE);
if (!glszm_arr)
{
free(tempData);
free(angles);
PyErr_SetString(PyExc_RuntimeError, "Failed to initialize output array for GLSZM");
return NULL;
}

glszm = (double *)PyArray_DATA(glszm_arr);

// Set all elements to 0
Expand Down Expand Up @@ -382,7 +398,7 @@ static PyObject *cmatrices_calculate_glrlm(PyObject *self, PyObject *args)
dims[2] = n_a;

// Check that the maximum size of the array won't overflow the index variable (int32)
if (dims[0] * dims[1] * dims[2] > 2147483648)
if (dims[0] * dims[1] * dims[2] > INT_MAX)
{
free(angles);
Py_XDECREF(image_arr);
Expand All @@ -392,6 +408,14 @@ static PyObject *cmatrices_calculate_glrlm(PyObject *self, PyObject *args)
}

glrlm_arr = (PyArrayObject *)PyArray_SimpleNew(3, dims, NPY_DOUBLE);
if (!glrlm_arr)
{
Py_XDECREF(image_arr);
Py_XDECREF(mask_arr);
free(angles);
PyErr_SetString(PyExc_RuntimeError, "Failed to initialize output array for GLRLM");
return NULL;
}

// Get arrays in Ctype
image = (int *)PyArray_DATA(image_arr);
Expand Down Expand Up @@ -458,7 +482,7 @@ static PyObject *cmatrices_calculate_ngtdm(PyObject *self, PyObject *args)
// Interpret the distance object as numpy array
distances_arr = (PyArrayObject *)PyArray_FROM_OTF(distances_obj, NPY_INT, NPY_ARRAY_FORCECAST | NPY_ARRAY_UPDATEIFCOPY | NPY_ARRAY_IN_ARRAY);

if (distances_arr == NULL)
if (!distances_arr)
{
Py_XDECREF(image_arr);
Py_XDECREF(mask_arr);
Expand Down Expand Up @@ -500,7 +524,7 @@ static PyObject *cmatrices_calculate_ngtdm(PyObject *self, PyObject *args)
dims[1] = 3;

// Check that the maximum size of the array won't overflow the index variable (int32)
if (dims[0] * dims[1] > 2147483648)
if (dims[0] * dims[1] > INT_MAX)
{
free(angles);
Py_XDECREF(image_arr);
Expand All @@ -510,6 +534,14 @@ static PyObject *cmatrices_calculate_ngtdm(PyObject *self, PyObject *args)
}

ngtdm_arr = (PyArrayObject *)PyArray_SimpleNew(2, dims, NPY_DOUBLE);
if (!ngtdm_arr)
{
Py_XDECREF(image_arr);
Py_XDECREF(mask_arr);
free(angles);
PyErr_SetString(PyExc_RuntimeError, "Failed to initialize output array for NGTDM");
return NULL;
}

// Get arrays in Ctype
image = (int *)PyArray_DATA(image_arr);
Expand Down Expand Up @@ -576,7 +608,7 @@ static PyObject *cmatrices_calculate_gldm(PyObject *self, PyObject *args)
// Interpret the distance object as numpy array
distances_arr = (PyArrayObject *)PyArray_FROM_OTF(distances_obj, NPY_INT, NPY_ARRAY_FORCECAST | NPY_ARRAY_UPDATEIFCOPY | NPY_ARRAY_IN_ARRAY);

if (distances_arr == NULL)
if (!distances_arr)
{
Py_XDECREF(image_arr);
Py_XDECREF(mask_arr);
Expand Down Expand Up @@ -618,7 +650,7 @@ static PyObject *cmatrices_calculate_gldm(PyObject *self, PyObject *args)
dims[1] = n_a * 2 + 1; // No of possible dependency values = Na *2 + 1 (Na angels, 2 directions and +1 for no dependency)

// Check that the maximum size of the array won't overflow the index variable (int32)
if (dims[0] * dims[1] > 2147483648)
if (dims[0] * dims[1] > INT_MAX)
{
free(angles);
Py_XDECREF(image_arr);
Expand All @@ -628,6 +660,14 @@ static PyObject *cmatrices_calculate_gldm(PyObject *self, PyObject *args)
}

gldm_arr = (PyArrayObject *)PyArray_SimpleNew(2, dims, NPY_DOUBLE);
if (!gldm_arr)
{
Py_XDECREF(image_arr);
Py_XDECREF(mask_arr);
free(angles);
PyErr_SetString(PyExc_RuntimeError, "Failed to initialize output array for GLDM");
return NULL;
}

// Get arrays in Ctype
image = (int *)PyArray_DATA(image_arr);
Expand Down Expand Up @@ -685,7 +725,7 @@ static PyObject *cmatrices_generate_angles(PyObject *self, PyObject *args)
size_arr = (PyArrayObject *)PyArray_FROM_OTF(size_obj, NPY_INT, NPY_ARRAY_FORCECAST | NPY_ARRAY_UPDATEIFCOPY | NPY_ARRAY_IN_ARRAY);
distances_arr = (PyArrayObject *)PyArray_FROM_OTF(distances_obj, NPY_INT, NPY_ARRAY_FORCECAST | NPY_ARRAY_UPDATEIFCOPY | NPY_ARRAY_IN_ARRAY);

if (size_arr == NULL || distances_arr == NULL)
if (!size_arr || !distances_arr)
{
Py_XDECREF(size_arr);
Py_XDECREF(distances_arr);
Expand Down Expand Up @@ -738,7 +778,7 @@ static PyObject *cmatrices_generate_angles(PyObject *self, PyObject *args)

int check_arrays(PyArrayObject *image_arr, PyArrayObject *mask_arr, int *size, int *strides)
{
if (image_arr == NULL || mask_arr == NULL)
if (!image_arr || !mask_arr)
{
Py_XDECREF(image_arr);
Py_XDECREF(mask_arr);
Expand Down

0 comments on commit 69933c7

Please sign in to comment.