Beispiel #1
0
  def _applyFilter(self, imageType):
    if imageType == 'original':
      self._current_image, self._current_mask = imageoperations.cropToTumorMask(self._image, self._mask, self._bb)
    else:
      raise NotImplementedError()

    self._imageType = imageType
Beispiel #2
0
  def generate_scenarios():
    global logger, testCases, baselineFile

    assert os.path.isfile(baselineFile)

    baseline = numpy.load(baselineFile)
    wavelets = ('HHH', 'HHL', 'HLH', 'HLL', 'LHH', 'LHL', 'LLH', 'LLL')
    baselineDict = {wavelets[idx]: b for idx, b in enumerate(baseline)}

    for testCase in testCases:
      im_path, ma_path = getTestCase(testCase)
      assert im_path is not None
      assert ma_path is not None

      logger.debug('Loading image and mask for testCase %s', testCase)
      image = sitk.ReadImage(im_path)
      mask = sitk.ReadImage(ma_path)

      logger.debug('Checking image and mask for testCase %s', testCase)
      bb, correctedMask = imageoperations.checkMask(image, mask)
      assert bb is not None  # Check mask should pass normally

      waveletGenerator = imageoperations.getWaveletImage(image, mask)
      for wavelet_image, wavelet_name, args in waveletGenerator:
        level = wavelet_name.split('-')[1]
        yield '_'.join((testCase, wavelet_name)), image, mask, baselineDict[level]

      logger.debug('Applying preCropping with padDistance 6 to testCase %s', testCase)
      image, mask = imageoperations.cropToTumorMask(image, mask, bb, padDistance=6)

      waveletGenerator = imageoperations.getWaveletImage(image, mask)
      for wavelet_image, wavelet_name, args in waveletGenerator:
        level = wavelet_name.split('-')[1]
        yield '_'.join((testCase, 'preCropped', wavelet_name)), image, mask, baselineDict[level]
Beispiel #3
0
  def computeShape(self, image, mask, boundingBox, **kwargs):
    """
    Calculate the shape (2D and/or 3D) features for the passed image and mask.

    :param image: SimpleITK.Image object representing the image used
    :param mask: SimpleITK.Image object representing the mask used
    :param boundingBox: The boundingBox calculated by :py:func:`~imageoperations.checkMask()`, i.e. a tuple with lower
      (even indices) and upper (odd indices) bound of the bounding box for each dimension.
    :param kwargs: Dictionary containing the settings to use.
    :return: collections.OrderedDict containing the calculated shape features. If no features are calculated, an empty
      OrderedDict will be returned.
    """
    global logger
    featureVector = collections.OrderedDict()

    enabledFeatures = self.enabledFeatures

    croppedImage, croppedMask = imageoperations.cropToTumorMask(image, mask, boundingBox)

    # Define temporary function to compute shape features
    def compute(shape_type):
      logger.info('Computing %s', shape_type)
      featureNames = enabledFeatures[shape_type]
      shapeClass = getFeatureClasses()[shape_type](croppedImage, croppedMask, **kwargs)

      if featureNames is not None:
        for feature in featureNames:
          shapeClass.enableFeatureByName(feature)

      for (featureName, featureValue) in six.iteritems(shapeClass.execute()):
        newFeatureName = 'original_%s_%s' % (shape_type, featureName)
        featureVector[newFeatureName] = featureValue

    Nd = mask.GetDimension()
    if 'shape' in enabledFeatures.keys():
      if Nd == 3:
        compute('shape')
      else:
        logger.warning('Shape features are only available 3D input (for 2D input, use shape2D). Found %iD input',
                       Nd)

    if 'shape2D' in enabledFeatures.keys():
      if Nd == 3:
        force2D = kwargs.get('force2D', False)
        force2Ddimension = kwargs.get('force2Ddimension', 0)
        if not force2D:
          logger.warning('parameter force2D must be set to True to enable shape2D extraction')
        elif not (boundingBox[1::2] - boundingBox[0::2] + 1)[force2Ddimension] > 1:
          logger.warning('Size in specified 2D dimension (%i) is greater than 1, cannot calculate 2D shape',
                         force2Ddimension)
        else:
          compute('shape2D')
      elif Nd == 2:
        compute('shape2D')
      else:
        logger.warning('Shape2D features are only available for 2D and 3D (with force2D=True) input. '
                       'Found %iD input', Nd)

    return featureVector
Beispiel #4
0
    def loadImage(self, ImageFilePath, MaskFilePath):
        """
    Preprocess the image and labelmap.
    If ImageFilePath is a string, it is loaded as SimpleITK Image and assigned to image,
    if it already is a SimpleITK Image, it is just assigned to image.
    All other cases are ignored (nothing calculated).
    Equal approach is used for assignment of mask using MaskFilePath.

    If normalizing is enabled image is first normalized before any resampling is applied.

    If resampling is enabled, both image and mask are resampled and cropped to the tumor mask (with additional
    padding as specified in padDistance) after assignment of image and mask.
    """
        self.logger.info('Loading image and mask')
        if isinstance(ImageFilePath,
                      six.string_types) and os.path.isfile(ImageFilePath):
            image = sitk.ReadImage(ImageFilePath)
        elif isinstance(ImageFilePath, sitk.SimpleITK.Image):
            image = ImageFilePath
        else:
            self.logger.warning(
                'Error reading image Filepath or SimpleITK object')
            return None, None  # this function is expected to always return a tuple of 2 elements

        if isinstance(MaskFilePath,
                      six.string_types) and os.path.isfile(MaskFilePath):
            mask = sitk.ReadImage(MaskFilePath)
        elif isinstance(MaskFilePath, sitk.SimpleITK.Image):
            mask = MaskFilePath
        else:
            self.logger.warning(
                'Error reading mask Filepath or SimpleITK object')
            return None, None  # this function is expected to always return a tuple of 2 elements

        # This point is only reached if image and mask loaded correctly
        if self.settings['normalize']:
            image = imageoperations.normalizeImage(
                image, self.settings['normalizeScale'],
                self.settings['removeOutliers'])

        if self.settings['interpolator'] is not None and self.settings[
                'resampledPixelSpacing'] is not None:
            image, mask = imageoperations.resampleImage(
                image, mask, self.settings['resampledPixelSpacing'],
                self.settings['interpolator'], self.settings['label'],
                self.settings['padDistance'])
        elif self.settings['preCrop']:
            bb, correctedMask = imageoperations.checkMask(
                image, mask, **self.settings)
            if correctedMask is not None:
                # Update the mask if it had to be resampled
                mask = correctedMask
            if bb is None:
                # Mask checks failed
                return None, None
            image, mask = imageoperations.cropToTumorMask(
                image, mask, bb, self.settings['padDistance'])

        return image, mask
Beispiel #5
0
    def setFeatureClassAndTestCase(self, className, testCase):
        """
    Set testing suite to specified testCase and feature class. Throws an assertion error if either class or test case
    are not recognized. These have to be set here together, as the settings with which the test case has to be loaded
    are defined per feature class in the baseline (extracted from provenance information).

    Only (re)loads an image/mask if the test case has changed, or the change of feature class causes a change in test
    settings.

    If feature class and test case are unchanged, nothing is reloaded and function returns False. If either feature
    class or test case is changed, function returns True.
    """
        if self._featureClassName == className and self._testCase == testCase:
            return False

        # First set featureClass if necessary, because if settings have changed, testCase needs te be reloaded
        if self._featureClassName != className:
            self._logger.debug('Setting feature class name to %s', className)
            assert className in self.getFeatureClasses()

            self._featureClassName = className

            # Check if test settings have changed
            if self._kwargs != self.getBaselineSettings(className, testCase):
                self._kwargs = self.getBaselineSettings(className, testCase)
                self._testCase = None  # forces image to be reloaded (as settings have changed)

        # Next, set testCase if necessary
        if self._testCase != testCase:
            self._logger.info("Reading the image and mask for test case %s",
                              testCase)
            assert testCase in self.getTestCases()
            self._testedSet.add(testCase)

            imageName = str(
                os.path.join(self._dataDir, testCase + '_image.nrrd'))
            maskName = str(
                os.path.join(self._dataDir, testCase + '_label.nrrd'))

            self._image = sitk.ReadImage(imageName)
            self._mask = sitk.ReadImage(maskName)

            interpolator = self._kwargs.get('interpolator', sitk.sitkBSpline)
            resampledPixelSpacing = self._kwargs.get('resampledPixelSpacing',
                                                     None)

            if interpolator is not None and resampledPixelSpacing is not None:
                self._image, self._mask = imageoperations.resampleImage(
                    self._image, self._mask, resampledPixelSpacing,
                    interpolator, self._kwargs.get('label', 1),
                    self._kwargs.get('padDistance', 5))
            bb = imageoperations.checkMask(self._image, self._mask)
            self._image, self._mask = imageoperations.cropToTumorMask(
                self._image, self._mask, bb, self._kwargs.get('label', 1))
            self._testCase = testCase

        return True
Beispiel #6
0
def demo():
    imageName, maskName = getTestCase('brain1')
    image = sitk.ReadImage(imageName)
    mask = sitk.ReadImage(maskName)
    settings = {
        'binWidth': 25,
        'interpolator': sitk.sitkBSpline,
        'resampledPixelSpacing': None
    }

    #
    # If enabled, resample image (resampled image is automatically cropped.
    #
    interpolator = settings.get('interpolator')
    resampledPixelSpacing = settings.get('resampledPixelSpacing')
    if interpolator is not None and resampledPixelSpacing is not None:
        image, mask = imageoperations.resampleImage(image, mask, **settings)

    bb, correctedMask = imageoperations.checkMask(image, mask)
    if correctedMask is not None:
        mask = correctedMask
    image, mask = imageoperations.cropToTumorMask(image, mask, bb)
    ###
    firstOrderFeatures = firstorder.RadiomicsFirstOrder(
        image, mask, **settings)
    results = firstOrderFeatures.execute()
    print(results)
    ###
    shapeFeatures = shape.RadiomicsShape(image, mask, **settings)
    shapeFeatures.enableAllFeatures()
    results = shapeFeatures.execute()
    ###
    glcmFeatures = glcm.RadiomicsGLCM(image, mask, **settings)
    glcmFeatures.enableAllFeatures()
    results = glcmFeatures.execute()
    ###
    glrlmFeatures = glrlm.RadiomicsGLRLM(image, mask, **settings)
    glrlmFeatures.enableAllFeatures()
    results = glrlmFeatures.execute()
    ###
    glszmFeatures = glszm.RadiomicsGLSZM(image, mask, **settings)
    glszmFeatures.enableAllFeatures()
    results = glszmFeatures.execute()
Beispiel #7
0
    def computeShape(self, image, mask, boundingBox, shape_type='shape'):
        featureVector = collections.OrderedDict()

        croppedImage, croppedMask = imageoperations.cropToTumorMask(
            image, mask, boundingBox)
        enabledFeatures = self._enabledFeatures[shape_type]

        self.logger.info('Computing shape')
        shapeClass = self.featureClasses[shape_type](croppedImage, croppedMask,
                                                     **self.settings)
        if enabledFeatures is None or len(enabledFeatures) == 0:
            shapeClass.enableAllFeatures()
        else:
            for feature in enabledFeatures:
                shapeClass.enableFeatureByName(feature)

        for (featureName, featureValue) in six.iteritems(shapeClass.execute()):
            newFeatureName = 'original_%s_%s' % (shape_type, featureName)
            featureVector[newFeatureName] = featureValue

        return featureVector
Beispiel #8
0
    def execute(self,
                imageFilepath,
                maskFilepath,
                label=None,
                voxelBased=False):
        """
    Compute radiomics signature for provide image and mask combination. It comprises of the following steps:

    1. Image and mask are loaded and normalized/resampled if necessary.
    2. Validity of ROI is checked using :py:func:`~imageoperations.checkMask`, which also computes and returns the
       bounding box.
    3. If enabled, provenance information is calculated and stored as part of the result. (Not available in voxel-based
       extraction)
    4. Shape features are calculated on a cropped (no padding) version of the original image. (Not available in
       voxel-based extraction)
    5. If enabled, resegment the mask based upon the range specified in ``resegmentRange`` (default None: resegmentation
       disabled).
    6. Other enabled feature classes are calculated using all specified image types in ``_enabledImageTypes``. Images
       are cropped to tumor mask (no padding) after application of any filter and before being passed to the feature
       class.
    7. The calculated features is returned as ``collections.OrderedDict``.

    :param imageFilepath: SimpleITK Image, or string pointing to image file location
    :param maskFilepath: SimpleITK Image, or string pointing to labelmap file location
    :param label: Integer, value of the label for which to extract features. If not specified, last specified label
        is used. Default label is 1.
    :returns: dictionary containing calculated signature ("<imageType>_<featureClass>_<featureName>":value).
    """
        if self.geometryTolerance != self.settings.get('geometryTolerance'):
            self._setTolerance()

        if label is not None:
            self.settings['label'] = label

        self.settings['voxelBased'] = voxelBased
        if voxelBased:
            self.logger.info('Starting voxel based extraction')

        self.logger.info('Calculating features with label: %d',
                         self.settings['label'])
        self.logger.debug('Enabled images types: %s', self._enabledImagetypes)
        self.logger.debug('Enabled features: %s', self._enabledFeatures)
        self.logger.debug('Current settings: %s', self.settings)

        # 1. Load the image and mask
        featureVector = collections.OrderedDict()
        image, mask = self.loadImage(imageFilepath, maskFilepath)

        if image is None or mask is None:
            # No features can be extracted, return the empty featureVector
            return featureVector

        # 2. Check whether loaded mask contains a valid ROI for feature extraction and get bounding box
        boundingBox, correctedMask = imageoperations.checkMask(
            image, mask, **self.settings)

        # Update the mask if it had to be resampled
        if correctedMask is not None:
            mask = correctedMask

        if boundingBox is None:
            # Mask checks failed, do not extract features and return the empty featureVector
            return featureVector

        self.logger.debug(
            'Image and Mask loaded and valid, starting extraction')

        if not voxelBased:
            # 3. Add the additional information if enabled
            if self.settings['additionalInfo']:
                featureVector.update(
                    self.getProvenance(imageFilepath, maskFilepath, mask))

            # 4. If shape descriptors should be calculated, handle it separately here
            if 'shape' in self._enabledFeatures.keys():
                croppedImage, croppedMask = imageoperations.cropToTumorMask(
                    image, mask, boundingBox)
                enabledFeatures = self._enabledFeatures['shape']

                self.logger.info('Computing shape')
                shapeClass = self.featureClasses['shape'](croppedImage,
                                                          croppedMask,
                                                          **self.settings)
                if enabledFeatures is None or len(enabledFeatures) == 0:
                    shapeClass.enableAllFeatures()
                else:
                    for feature in enabledFeatures:
                        shapeClass.enableFeatureByName(feature)

                shapeClass.calculateFeatures()
                for (featureName,
                     featureValue) in six.iteritems(shapeClass.featureValues):
                    newFeatureName = 'original_shape_%s' % featureName
                    featureVector[newFeatureName] = featureValue

        # 5. Resegment the mask if enabled (parameter regsegmentMask is not None)
        resegmentRange = self.settings.get('resegmentRange', None)
        if resegmentRange is not None:
            resegmentedMask = imageoperations.resegmentMask(
                image, mask, resegmentRange, self.settings['label'])

            # Recheck to see if the mask is still valid
            boundingBox, correctedMask = imageoperations.checkMask(
                image, resegmentedMask, **self.settings)
            # Update the mask if it had to be resampled
            if correctedMask is not None:
                resegmentedMask = correctedMask

            if boundingBox is None:
                # Mask checks failed, do not extract features and return the empty featureVector
                return featureVector

            # Resegmentation successful
            mask = resegmentedMask

        # 6. Calculate other enabled feature classes using enabled image types
        # Make generators for all enabled image types
        self.logger.debug('Creating image type iterator')
        imageGenerators = []
        for imageType, customKwargs in six.iteritems(self._enabledImagetypes):
            args = self.settings.copy()
            args.update(customKwargs)
            self.logger.info(
                'Adding image type "%s" with custom settings: %s' %
                (imageType, str(customKwargs)))
            imageGenerators = chain(
                imageGenerators,
                getattr(imageoperations, 'get%sImage' % imageType)(image, mask,
                                                                   **args))

        self.logger.debug('Extracting features')
        # Calculate features for all (filtered) images in the generator
        for inputImage, imageTypeName, inputKwargs in imageGenerators:
            self.logger.info('Calculating features for %s image',
                             imageTypeName)
            inputImage, inputMask = imageoperations.cropToTumorMask(
                inputImage, mask, boundingBox)
            featureVector.update(
                self.computeFeatures(inputImage, inputMask, imageTypeName,
                                     **inputKwargs))

        self.logger.debug('Features extracted')

        return featureVector
Beispiel #9
0
    def loadImage(self, ImageFilePath, MaskFilePath):
        """
    Preprocess the image and labelmap.
    If ImageFilePath is a string, it is loaded as SimpleITK Image and assigned to image,
    if it already is a SimpleITK Image, it is just assigned to image.
    All other cases are ignored (nothing calculated).
    Equal approach is used for assignment of mask using MaskFilePath.

    If normalizing is enabled image is first normalized before any resampling is applied.

    If resampling is enabled, both image and mask are resampled and cropped to the tumor mask (with additional
    padding as specified in padDistance) after assignment of image and mask.
    """
        normalize = self.settings.get('normalize', False)
        interpolator = self.settings.get('interpolator')
        resampledPixelSpacing = self.settings.get('resampledPixelSpacing')
        preCrop = self.settings.get('preCrop', False)
        label = self.settings.get('label', 1)

        self.logger.info('Loading image and mask')
        if isinstance(ImageFilePath,
                      six.string_types) and os.path.isfile(ImageFilePath):
            image = sitk.ReadImage(ImageFilePath)
        elif isinstance(ImageFilePath, sitk.SimpleITK.Image):
            image = ImageFilePath
        else:
            raise ValueError(
                'Error reading image Filepath or SimpleITK object')

        if isinstance(MaskFilePath,
                      six.string_types) and os.path.isfile(MaskFilePath):
            mask = sitk.ReadImage(MaskFilePath, sitk.sitkUInt32)
        elif isinstance(MaskFilePath, sitk.SimpleITK.Image):
            mask = sitk.Cast(MaskFilePath, sitk.sitkUInt32)
        else:
            raise ValueError('Error reading mask Filepath or SimpleITK object')

        if self.generalInfo is not None:
            self.generalInfo.addImageElements(image)
            # Do not include the image here, as the overlap between image and mask have not been checked
            # It is therefore possible that image and mask do not align, or even have different sizes.
            self.generalInfo.addMaskElements(None, mask, label)

        # This point is only reached if image and mask loaded correctly
        if normalize:
            image = imageoperations.normalizeImage(image, **self.settings)

        if interpolator is not None and resampledPixelSpacing is not None:
            image, mask = imageoperations.resampleImage(
                image, mask, **self.settings)
            if self.generalInfo is not None:
                self.generalInfo.addImageElements(image, 'interpolated')
                self.generalInfo.addMaskElements(image, mask,
                                                 self.settings.get('label', 1),
                                                 'interpolated')

        elif preCrop:
            bb, correctedMask = imageoperations.checkMask(
                image, mask, **self.settings)
            if correctedMask is not None:
                # Update the mask if it had to be resampled
                mask = correctedMask
            if bb is None:
                # Mask checks failed
                raise ValueError('Mask checks failed during pre-crop')

            image, mask = imageoperations.cropToTumorMask(
                image, mask, bb, **self.settings)

        return image, mask
Beispiel #10
0
    def execute(self,
                imageFilepath,
                maskFilepath,
                label=None,
                voxelBased=False):
        """
    Compute radiomics signature for provide image and mask combination. It comprises of the following steps:

    1. Image and mask are loaded and normalized/resampled if necessary.
    2. Validity of ROI is checked using :py:func:`~imageoperations.checkMask`, which also computes and returns the
       bounding box.
    3. If enabled, provenance information is calculated and stored as part of the result. (Not available in voxel-based
       extraction)
    4. Shape features are calculated on a cropped (no padding) version of the original image. (Not available in
       voxel-based extraction)
    5. If enabled, resegment the mask based upon the range specified in ``resegmentRange`` (default None: resegmentation
       disabled).
    6. Other enabled feature classes are calculated using all specified image types in ``_enabledImageTypes``. Images
       are cropped to tumor mask (no padding) after application of any filter and before being passed to the feature
       class.
    7. The calculated features is returned as ``collections.OrderedDict``.

    :param imageFilepath: SimpleITK Image, or string pointing to image file location
    :param maskFilepath: SimpleITK Image, or string pointing to labelmap file location
    :param label: Integer, value of the label for which to extract features. If not specified, last specified label
        is used. Default label is 1.
    :returns: dictionary containing calculated signature ("<imageType>_<featureClass>_<featureName>":value).
    """
        geometryTolerance = self.settings.get('geometryTolerance')
        additionalInfo = self.settings.get('additionalInfo', False)
        resegmentShape = self.settings.get('resegmentShape', False)

        if label is not None:
            self.settings['label'] = label
        else:
            label = self.settings.get('label', 1)

        if self.geometryTolerance != geometryTolerance:
            self._setTolerance()

        if additionalInfo:
            self.generalInfo = generalinfo.GeneralInfo()
            self.generalInfo.addGeneralSettings(self.settings)
            self.generalInfo.addEnabledImageTypes(self._enabledImagetypes)
        else:
            self.generalInfo = None

        if voxelBased:
            self.settings['voxelBased'] = True
            self.logger.info('Starting voxel based extraction')

        self.logger.info('Calculating features with label: %d', label)
        self.logger.debug('Enabled images types: %s', self._enabledImagetypes)
        self.logger.debug('Enabled features: %s', self._enabledFeatures)
        self.logger.debug('Current settings: %s', self.settings)

        if self.settings.get('binCount', None) is not None:
            self.logger.warning(
                'Fixed bin Count enabled! However, we recommend using a fixed bin Width. See '
                'http://pyradiomics.readthedocs.io/en/latest/faq.html#radiomics-fixed-bin-width for more '
                'details')

        # 1. Load the image and mask
        featureVector = collections.OrderedDict()
        image, mask = self.loadImage(imageFilepath, maskFilepath)

        # 2. Check whether loaded mask contains a valid ROI for feature extraction and get bounding box
        # Raises a ValueError if the ROI is invalid
        boundingBox, correctedMask = imageoperations.checkMask(
            image, mask, **self.settings)

        # Update the mask if it had to be resampled
        if correctedMask is not None:
            if self.generalInfo is not None:
                self.generalInfo.addMaskElements(image, correctedMask, label,
                                                 'corrected')
            mask = correctedMask

        self.logger.debug(
            'Image and Mask loaded and valid, starting extraction')

        # 5. Resegment the mask if enabled (parameter regsegmentMask is not None)
        resegmentedMask = None
        if self.settings.get('resegmentRange', None) is not None:
            resegmentedMask = imageoperations.resegmentMask(
                image, mask, **self.settings)

            # Recheck to see if the mask is still valid, raises a ValueError if not
            boundingBox, correctedMask = imageoperations.checkMask(
                image, resegmentedMask, **self.settings)

            if self.generalInfo is not None:
                self.generalInfo.addMaskElements(image, resegmentedMask, label,
                                                 'resegmented')

        # if resegmentShape is True and resegmentation has been enabled, update the mask here to also use the
        # resegmented mask for shape calculation (e.g. PET resegmentation)
        if resegmentShape and resegmentedMask is not None:
            mask = resegmentedMask

        if not voxelBased:
            # 3. Add the additional information if enabled
            if self.generalInfo is not None:
                featureVector.update(self.generalInfo.getGeneralInfo())

            # 4. If shape descriptors should be calculated, handle it separately here
            if 'shape' in self._enabledFeatures.keys():
                featureVector.update(
                    self.computeShape(image, mask, boundingBox))
            if 'shape2D' in self._enabledFeatures.keys():
                force2D = self.settings.get('force2D', False)
                force2Ddimension = self.settings.get('force2Ddimension', 0)
                if not force2D:
                    self.logger.warning(
                        'parameter force2D must be set to True to enable shape2D extraction'
                    )
                elif not (boundingBox[1::2] - boundingBox[0::2] +
                          1)[force2Ddimension] > 1:
                    self.logger.warning(
                        'Size in specified 2D dimension (%i) is greater than 1, cannot calculate 2D shape',
                        force2Ddimension)
                else:
                    featureVector.update(
                        self.computeShape(image, mask, boundingBox, 'shape2D'))

        # (Default) Only use resegemented mask for feature classes other than shape
        # can be overridden by specifying `resegmentShape` = True
        if not resegmentShape and resegmentedMask is not None:
            mask = resegmentedMask

        # 6. Calculate other enabled feature classes using enabled image types
        # Make generators for all enabled image types
        self.logger.debug('Creating image type iterator')
        imageGenerators = []
        for imageType, customKwargs in six.iteritems(self._enabledImagetypes):
            args = self.settings.copy()
            args.update(customKwargs)
            self.logger.info(
                'Adding image type "%s" with custom settings: %s' %
                (imageType, str(customKwargs)))
            imageGenerators = chain(
                imageGenerators,
                getattr(imageoperations, 'get%sImage' % imageType)(image, mask,
                                                                   **args))

        self.logger.debug('Extracting features')
        # Calculate features for all (filtered) images in the generator
        for inputImage, imageTypeName, inputKwargs in imageGenerators:
            self.logger.info('Calculating features for %s image',
                             imageTypeName)
            inputImage, inputMask = imageoperations.cropToTumorMask(
                inputImage, mask, boundingBox)
            featureVector.update(
                self.computeFeatures(inputImage, inputMask, imageTypeName,
                                     **inputKwargs))

        self.logger.debug('Features extracted')

        return featureVector
Beispiel #11
0
    def loadImage(ImageFilePath,
                  MaskFilePath,
                  otherImageFilePath,
                  generalInfo=None,
                  **kwargs):
        """
    Load and pre-process the image and labelmap.
    If ImageFilePath is a string, it is loaded as SimpleITK Image and assigned to ``image``,
    if it already is a SimpleITK Image, it is just assigned to ``image``.
    All other cases are ignored (nothing calculated).
    Equal approach is used for assignment of ``mask`` using MaskFilePath. If necessary, a segmentation object (i.e. mask
    volume with vector-image type) is then converted to a labelmap (=scalar image type). Data type is forced to UInt32.
    See also :py:func:`~imageoperations.getMask()`.

    If normalizing is enabled image is first normalized before any resampling is applied.

    If resampling is enabled, both image and mask are resampled and cropped to the tumor mask (with additional
    padding as specified in padDistance) after assignment of image and mask.

    :param ImageFilePath: SimpleITK.Image object or string pointing to SimpleITK readable file representing the image
                          to use.
    :param MaskFilePath: SimpleITK.Image object or string pointing to SimpleITK readable file representing the mask
                         to use.
    :param generalInfo: GeneralInfo Object. If provided, it is used to store diagnostic information of the
                        pre-processing.
    :param kwargs: Dictionary containing the settings to use for this particular image type.
    :return: 2 SimpleITK.Image objects representing the loaded image and mask, respectively.
    """
        global logger

        normalize = kwargs.get('normalize', False)
        interpolator = kwargs.get('interpolator')
        resampledPixelSpacing = kwargs.get('resampledPixelSpacing')
        preCrop = kwargs.get('preCrop', False)
        label = kwargs.get('label', 1)

        logger.info('Loading image and mask')
        if isinstance(ImageFilePath,
                      six.string_types) and os.path.isfile(ImageFilePath):
            image = sitk.ReadImage(ImageFilePath)
        elif isinstance(ImageFilePath, sitk.SimpleITK.Image):
            image = ImageFilePath
        else:
            raise ValueError(
                'Error reading image Filepath or SimpleITK object')

        if isinstance(MaskFilePath,
                      six.string_types) and os.path.isfile(MaskFilePath):
            mask = sitk.ReadImage(MaskFilePath)
        elif isinstance(MaskFilePath, sitk.SimpleITK.Image):
            mask = MaskFilePath
        else:
            raise ValueError('Error reading mask Filepath or SimpleITK object')

        # Read other image for washin
        if otherImageFilePath is not None:
            if isinstance(
                    otherImageFilePath,
                    six.string_types) and os.path.isfile(otherImageFilePath):
                imageOther = sitk.ReadImage(otherImageFilePath)
            elif isinstance(otherImageFilePath, sitk.SimpleITK.Image):
                imageOther = otherImageFilePath
            else:
                raise ValueError(
                    'Error reading image Filepath or SimpleITK object')

            # Align input types
            caster = sitk.CastImageFilter()
            caster.SetOutputPixelType(sitk.sitkFloat64)
            image = caster.Execute(image)
            imageOther = caster.Execute(imageOther)

            # t1 - t0
            f_subtract = sitk.SubtractImageFilter()
            wash = f_subtract.Execute(imageOther, image)
            # (t1 - t0) / t0
            f_divide = sitk.DivideImageFilter()
            wash = f_divide.Execute(wash, image)

            # Replace max Values with 0
            f_less = sitk.LessEqualImageFilter()
            value_mask = f_less.Execute(wash, sys.float_info.max / 2)
            f_mask = sitk.MaskImageFilter()
            image = f_mask.Execute(wash, value_mask)

        # process the mask
        mask = imageoperations.getMask(mask, **kwargs)

        if generalInfo is not None:
            generalInfo.addImageElements(image)
            # Do not include the image here, as the overlap between image and mask have not been checked
            # It is therefore possible that image and mask do not align, or even have different sizes.
            generalInfo.addMaskElements(None, mask, label)

        # This point is only reached if image and mask loaded correctly
        if normalize:
            image = imageoperations.normalizeImage(image, **kwargs)

        if interpolator is not None and resampledPixelSpacing is not None:
            image, mask = imageoperations.resampleImage(image, mask, **kwargs)
            if generalInfo is not None:
                generalInfo.addImageElements(image, 'interpolated')
                generalInfo.addMaskElements(image, mask, label, 'interpolated')

        elif preCrop:
            bb, correctedMask = imageoperations.checkMask(
                image, mask, **kwargs)
            if correctedMask is not None:
                # Update the mask if it had to be resampled
                mask = correctedMask
            if bb is None:
                # Mask checks failed
                raise ValueError('Mask checks failed during pre-crop')

            image, mask = imageoperations.cropToTumorMask(
                image, mask, bb, **kwargs)

        return image, mask
Beispiel #12
0
def main():
    dataDir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..",
                           "data")
    baselineDir = os.path.join(dataDir, "baseline")

    testCases = []

    kwargs = {
        'binWidth': 25,
        'interpolator': None,
        'resampledPixelSpacing': None,
        'padDistance': 5,
        'voxelArrayShift': 2000,
        'symmetricalGLCM': False,
        'weightingNorm': None,
        'gldm_a': 0
    }

    extractor = featureextractor.RadiomicsFeaturesExtractor(**kwargs)

    for cls in extractor.getFeatureClassNames():
        if os.path.exists(os.path.join(baselineDir,
                                       'baseline_%s.csv' % (cls))):
            with open(os.path.join(baselineDir, 'baseline_%s.csv' % (cls)),
                      'rb') as baselineFile:
                csvReader = csv.reader(baselineFile)
                six.next(csvReader)  # Skip header row
                for testRow in csvReader:
                    testCases += [testRow[0]]
            if len(testCases) > 0: break

    if len(testCases) == 0:
        print("No baselinefiles containing testcases found, exiting...")
        exit(-1)

    newClasses = [
        cls for cls in extractor.getFeatureClassNames() if
        not os.path.exists(os.path.join(baselineDir, 'baseline_%s.csv' %
                                        (cls)))
    ]

    if len(newClasses) == 0:
        print("No new classes to add, exiting...")
        exit(0)

    print("Adding new classes: ", newClasses)

    newBaseline = {}

    # When C matrices is merged, force baseline to be build using the Python implementation of the functions.
    # radiomics.pythonMatrixCalculation(True)

    extractor.disableAllFeatures()
    extractor.disableAllInputImages()

    # Calculate new baseline on original image for all new classes
    extractor.enableInputImageByName('original')
    for cls in newClasses:
        newBaseline[cls] = {}

    print("Computing new baseline")
    for testCase in testCases:
        print("\tCalculating test case", testCase)
        imagePath = os.path.join(dataDir, testCase + '_image.nrrd')
        maskPath = os.path.join(dataDir, testCase + '_label.nrrd')
        image, mask = extractor.loadImage(imagePath, maskPath)
        if image is None or mask is None:
            print("Error during loading of image/mask, testcase:", testCase)
            continue  # testImage or mask not found / error during loading

        provenance = extractor.getProvenance(imagePath, maskPath, mask)

        bb, correctedMask = imageoperations.checkMask(image, mask)
        if correctedMask is not None:
            mask = correctedMask
        image, mask = imageoperations.cropToTumorMask(image, mask, bb)
        for cls in newClasses:
            print("\t\tCalculating class", cls)
            newBaseline[cls][testCase] = collections.OrderedDict()
            newBaseline[cls][testCase]["Patient ID"] = testCase
            newBaseline[cls][testCase].update(provenance)
            featureClass = extractor.featureClasses[cls](image, mask,
                                                         **extractor.settings)
            featureClass.enableAllFeatures()
            featureClass.calculateFeatures()
            newBaseline[cls][testCase].update(featureClass.featureValues)

    print("Writing new baseline")
    for cls in newClasses:
        baselineFile = os.path.join(baselineDir, 'baseline_%s.csv' % (cls))
        with open(baselineFile, 'wb') as baseline:
            csvWriter = csv.writer(baseline)
            header = newBaseline[cls][testCases[0]].keys()
            csvWriter.writerow(header)
            for testCase in testCases:
                row = []
                for h in header:
                    row += [newBaseline[cls][testCase][h]]
                csvWriter.writerow(row)
        baseline.close()
Beispiel #13
0
def features_extractor(patients_nrrd_path, valid_IDs, applyLog = False, applyWavelet = False):
    feature_vectors = {}
    cnt = 0
    for case_id in valid_IDs:
        feature_vectors[case_id] = {}
        cnt += 1
        # try:
        ct_nrrd_path = os.path.join(patients_nrrd_path,case_id, "image.nrrd")
        ss_nrrd_path = os.path.join(patients_nrrd_path,case_id, "mask.nrrd")
        print("Reading ct image")
        image = sitk.ReadImage(ct_nrrd_path)
        # image, header = nrrd.read(ct_nrrd_path)
        print("Reading roi mask")
        mask = sitk.ReadImage(ss_nrrd_path)
        # mask, header = nrrd.read(ss_nrrd_path)
        print("Getting ct image array")
        image_array = sitk.GetArrayFromImage(image)
        print("Getting roi mask array")
        mask_array = sitk.GetArrayFromImage(mask)
        print(image_array.shape, mask_array.shape)
        # simple_plot_nrrd(image_array, mask_array, sliceNumber=75, plotSrc='sitk')
        print (cnt, "_ Calculating features: ",case_id)   

        settings = {'binWidth': 25,
                'interpolator': sitk.sitkBSpline,
                'resampledPixelSpacing': None}
        interpolator = settings.get('interpolator')
        resampledPixelSpacing = settings.get('resampledPixelSpacing')
        if interpolator is not None and resampledPixelSpacing is not None:
            image, mask = imageoperations.resampleImage(image, mask, **settings)
        bb, correctedMask = imageoperations.checkMask(image, mask)
        if correctedMask is not None:
          mask = correctedMask
        image, mask = imageoperations.cropToTumorMask(image, mask, bb)
        
        firstOrderFeatures = firstorder.RadiomicsFirstOrder(image, mask, **settings)
        # firstOrderFeatures.enableFeatureByName('Mean', True)
        firstOrderFeatures.enableAllFeatures()

        # print('Will calculate the following first order features: ')
        # for f in firstOrderFeatures.enabledFeatures.keys():
        #   print('  ', f)
        #   print(getattr(firstOrderFeatures, 'get%sFeatureValue' % f).__doc__)

        # print('Calculating first order features...')
        results = firstOrderFeatures.execute()
        # print('done')

        print('Calculated first order features: ')
        for (key, val) in six.iteritems(results):
            firstOrderFeatureName = '%s_%s' % ('firstOrder', key)
            if firstOrderFeatureName not in feature_vectors[case_id]:
                feature_vectors[case_id][firstOrderFeatureName] = val
            else:
                print('Error: firstOrder key existing! %s'%firstOrderFeatureName)
                # break
            # print('  ', key, ':', val)

        #
        # Show Shape features
        #

        shapeFeatures = shape.RadiomicsShape(image, mask, **settings)
        shapeFeatures.enableAllFeatures()

        # print('Will calculate the following Shape features: ')
        # for f in shapeFeatures.enabledFeatures.keys():
        #   print('  ', f)
        #   print(getattr(shapeFeatures, 'get%sFeatureValue' % f).__doc__)

        # print('Calculating Shape features...')
        results = shapeFeatures.execute()
        # print('done')

        print('Calculated Shape features: ')
        for (key, val) in six.iteritems(results):
            ShapeFeatureName = '%s_%s' % ('Shape', key)
            if ShapeFeatureName not in feature_vectors[case_id]:
                feature_vectors[case_id][ShapeFeatureName] = val
            else:
                print('Error: shape key existing! %s'%ShapeFeatureName)
                # break
            # print('  ', key, ':', val)

        #
        # Show GLCM features: Gray Level Co-occurrence Matrix (GLCM) Features
        #
        glcmFeatures = glcm.RadiomicsGLCM(image, mask, **settings)
        glcmFeatures.enableAllFeatures()

        # print('Will calculate the following GLCM features: ')
        # for f in glcmFeatures.enabledFeatures.keys():
        #   print('  ', f)
        #   print(getattr(glcmFeatures, 'get%sFeatureValue' % f).__doc__)

        # print('Calculating GLCM features...')
        results = glcmFeatures.execute()
        # print('done')

        print('Calculated GLCM features: ')
        for (key, val) in six.iteritems(results):
            GLCMFeatureName = '%s_%s' % ('GLCM', key)
            if GLCMFeatureName not in feature_vectors[case_id]:
                feature_vectors[case_id][GLCMFeatureName] = val
            else:
                print('Error: GLCM key existing! %s'%GLCMFeatureName)
                # break
            # print('  ', key, ':', val)



        #
        # Show GLSZM features; Gray Level Size Zone Matrix (GLSZM) Features
        #
        glszmFeatures = glszm.RadiomicsGLSZM(image, mask, **settings)
        glszmFeatures.enableAllFeatures()

        # print('Will calculate the following GLSZM features: ')
        # for f in glszmFeatures.enabledFeatures.keys():
        #   print('  ', f)
        #   print(getattr(glszmFeatures, 'get%sFeatureValue' % f).__doc__)

        # print('Calculating GLSZM features...')
        results = glszmFeatures.execute()
        print('done')

        print('Calculated GLSZM features: ')
        for (key, val) in six.iteritems(results):
            GLSZMFeatureName = '%s_%s' % ('GLSZM', key)
            if GLSZMFeatureName not in feature_vectors[case_id]:
                feature_vectors[case_id][GLSZMFeatureName] = val
            else:
                print('Error: GLSZM key existing! %s'%GLSZMFeatureName)
                # break
            # print('  ', key, ':', val)


        #
        # Show GLRLM features; Gray Level Run Length Matrix (GLRLM) Features
        #
        glrlmFeatures = glrlm.RadiomicsGLRLM(image, mask, **settings)
        glrlmFeatures.enableAllFeatures()

        # print('Will calculate the following GLRLM features: ')
        # for f in glrlmFeatures.enabledFeatures.keys():
        #   print('  ', f)
        #   print(getattr(glrlmFeatures, 'get%sFeatureValue' % f).__doc__)

        # print('Calculating GLRLM features...')
        results = glrlmFeatures.execute()
        # print('done')

        print('Calculated GLRLM features: ')
        for (key, val) in six.iteritems(results):
            GLRLMFeatureName = '%s_%s' % ('GLRLM', key)
            if GLRLMFeatureName not in feature_vectors[case_id]:
                feature_vectors[case_id][GLRLMFeatureName] = val
            else:
                print('Error: GLRLM key existing! %s'%GLRLMFeatureName)
                # break
            # print('  ', key, ':', val)

        #
        # Show NGTDM features; Neighbouring Gray Tone Difference Matrix (NGTDM) Features
        #
        ngtdmFeatures = ngtdm.RadiomicsNGTDM(image, mask, **settings)
        ngtdmFeatures.enableAllFeatures()

        # print('Will calculate the following NGTDM features: ')
        # for f in ngtdmFeatures.enabledFeatures.keys():
        #   print('  ', f)
        #   print(getattr(ngtdmFeatures, 'get%sFeatureValue' % f).__doc__)

        # print('Calculating NGTDM features...')
        results = ngtdmFeatures.execute()
        # print('done')

        print('Calculated NGTDM features: ')
        for (key, val) in six.iteritems(results):
            NGTDMFeatureName = '%s_%s' % ('NGTDM', key)
            if NGTDMFeatureName not in feature_vectors[case_id]:
                feature_vectors[case_id][NGTDMFeatureName] = val
            else:
                print('Error: NGTDM key existing! %s'%NGTDMFeatureName)
                # break
            # print('  ', key, ':', val)

        #
        # Show GLDM features; Gray Level Dependence Matrix (GLDM) Features
        #
        gldmFeatures = gldm.RadiomicsGLDM(image, mask, **settings)
        gldmFeatures.enableAllFeatures()

        # print('Will calculate the following GLDM features: ')
        # for f in gldmFeatures.enabledFeatures.keys():
        #   print('  ', f)
        #   print(getattr(gldmFeatures, 'get%sFeatureValue' % f).__doc__)

        # print('Calculating GLDM features...')
        results = gldmFeatures.execute()
        # print('done')

        print('Calculated GLDM features: ')
        for (key, val) in six.iteritems(results):
            GLDMFeatureName = '%s_%s' % ('GLDM', key)
            if GLDMFeatureName not in feature_vectors[case_id]:
                feature_vectors[case_id][GLDMFeatureName] = val
            else:
                print('Error: GLDM key existing! %s'%GLDMFeatureName)
                # break
            # print('  ', key, ':', val)


        #
        # Show FirstOrder features, calculated on a LoG filtered image
        #
        if applyLog:
          sigmaValues = np.arange(5., 0., -.5)[::1]
          for logImage, imageTypeName, inputKwargs in imageoperations.getLoGImage(image, mask, sigma=sigmaValues):
            logFirstorderFeatures = firstorder.RadiomicsFirstOrder(logImage, mask, **inputKwargs)
            logFirstorderFeatures.enableAllFeatures()
            results = logFirstorderFeatures.execute()
            for (key, val) in np.iteritems(results):
                laplacianFeatureName = '%s_%s' % (imageTypeName, key)
                if laplacianFeatureName not in feature_vectors[case_id]:
                    feature_vectors[case_id][laplacianFeatureName] = val
                else:
                    print('Error: LoG key existing! %s'%laplacianFeatureName)
                    # break
                # print('  ', laplacianFeatureName, ':', val)
        #
        # Show FirstOrder features, calculated on a wavelet filtered image
        #
        if applyWavelet:
          for decompositionImage, decompositionName, inputKwargs in imageoperations.getWaveletImage(image, mask):
            waveletFirstOrderFeaturs = firstorder.RadiomicsFirstOrder(decompositionImage, mask, **inputKwargs)
            waveletFirstOrderFeaturs.enableAllFeatures()
            results = waveletFirstOrderFeaturs.execute()
            print('Calculated firstorder features with wavelet ', decompositionName)
            for (key, val) in six.iteritems(results):
                waveletFeatureName = '%s_%s' % (str(decompositionName), key)
                if waveletFeatureName not in feature_vectors[case_id]:
                    feature_vectors[case_id][waveletFeatureName] = val
                else:
                    print('Error: wavelet key existing! %s'%waveletFeatureName)
                    # break
                # print('  ', waveletFeatureName, ':', val)

        mask = None
        image = None
    return feature_vectors
Beispiel #14
0
    def execute(self,
                imageFilepath,
                maskFilepath,
                otherImageFilePath=None,
                label=None,
                label_channel=None,
                voxelBased=False):
        """
    Compute radiomics signature for provide image and mask combination. It comprises of the following steps:

    1. Image and mask are loaded and normalized/resampled if necessary.
    2. Validity of ROI is checked using :py:func:`~imageoperations.checkMask`, which also computes and returns the
       bounding box.
    3. If enabled, provenance information is calculated and stored as part of the result. (Not available in voxel-based
       extraction)
    4. Shape features are calculated on a cropped (no padding) version of the original image. (Not available in
       voxel-based extraction)
    5. If enabled, resegment the mask based upon the range specified in ``resegmentRange`` (default None: resegmentation
       disabled).
    6. Other enabled feature classes are calculated using all specified image types in ``_enabledImageTypes``. Images
       are cropped to tumor mask (no padding) after application of any filter and before being passed to the feature
       class.
    7. The calculated features is returned as ``collections.OrderedDict``.

    :param imageFilepath: SimpleITK Image, or string pointing to image file location
    :param maskFilepath: SimpleITK Image, or string pointing to labelmap file location
    :param label: Integer, value of the label for which to extract features. If not specified, last specified label
        is used. Default label is 1.
    :param label_channel: Integer, index of the channel to use when maskFilepath yields a SimpleITK.Image with a vector
        pixel type. Default index is 0.
    :param voxelBased: Boolean, default False. If set to true, a voxel-based extraction is performed, segment-based
        otherwise.
    :returns: dictionary containing calculated signature ("<imageType>_<featureClass>_<featureName>":value).
        In case of segment-based extraction, value type for features is float, if voxel-based, type is SimpleITK.Image.
        Type of diagnostic features differs, but can always be represented as a string.
    """
        global geometryTolerance, logger
        _settings = self.settings.copy()

        tolerance = _settings.get('geometryTolerance')
        additionalInfo = _settings.get('additionalInfo', False)
        resegmentShape = _settings.get('resegmentShape', False)

        if label is not None:
            _settings['label'] = label
        else:
            label = _settings.get('label', 1)

        if label_channel is not None:
            _settings['label_channel'] = label_channel

        if geometryTolerance != tolerance:
            self._setTolerance()

        if additionalInfo:
            generalInfo = generalinfo.GeneralInfo()
            generalInfo.addGeneralSettings(_settings)
            generalInfo.addEnabledImageTypes(self.enabledImagetypes)
        else:
            generalInfo = None

        if voxelBased:
            _settings['voxelBased'] = True
            kernelRadius = _settings.get('kernelRadius', 1)
            logger.info('Starting voxel based extraction')
        else:
            kernelRadius = 0

        logger.info('Calculating features with label: %d', label)
        logger.debug('Enabled images types: %s', self.enabledImagetypes)
        logger.debug('Enabled features: %s', self.enabledFeatures)
        logger.debug('Current settings: %s', _settings)

        # 1. Load the image and mask
        featureVector = collections.OrderedDict()
        image, mask = self.loadImage(imageFilepath, maskFilepath,
                                     otherImageFilePath, generalInfo,
                                     **_settings)

        # 2. Check whether loaded mask contains a valid ROI for feature extraction and get bounding box
        # Raises a ValueError if the ROI is invalid
        boundingBox, correctedMask = imageoperations.checkMask(
            image, mask, **_settings)

        # Update the mask if it had to be resampled
        if correctedMask is not None:
            if generalInfo is not None:
                generalInfo.addMaskElements(image, correctedMask, label,
                                            'corrected')
            mask = correctedMask

        logger.debug('Image and Mask loaded and valid, starting extraction')

        # 5. Resegment the mask if enabled (parameter regsegmentMask is not None)
        resegmentedMask = None
        if _settings.get('resegmentRange', None) is not None:
            resegmentedMask = imageoperations.resegmentMask(
                image, mask, **_settings)

            # Recheck to see if the mask is still valid, raises a ValueError if not
            boundingBox, correctedMask = imageoperations.checkMask(
                image, resegmentedMask, **_settings)

            if generalInfo is not None:
                generalInfo.addMaskElements(image, resegmentedMask, label,
                                            'resegmented')

        # 3. Add the additional information if enabled
        if generalInfo is not None:
            featureVector.update(generalInfo.getGeneralInfo())

        # if resegmentShape is True and resegmentation has been enabled, update the mask here to also use the
        # resegmented mask for shape calculation (e.g. PET resegmentation)
        if resegmentShape and resegmentedMask is not None:
            mask = resegmentedMask

        if not voxelBased:
            # 4. If shape descriptors should be calculated, handle it separately here
            featureVector.update(
                self.computeShape(image, mask, boundingBox, **_settings))

        # (Default) Only use resegemented mask for feature classes other than shape
        # can be overridden by specifying `resegmentShape` = True
        if not resegmentShape and resegmentedMask is not None:
            mask = resegmentedMask

        # 6. Calculate other enabled feature classes using enabled image types
        # Make generators for all enabled image types
        logger.debug('Creating image type iterator')
        imageGenerators = []
        for imageType, customKwargs in six.iteritems(self.enabledImagetypes):
            args = _settings.copy()
            args.update(customKwargs)
            logger.info('Adding image type "%s" with custom settings: %s' %
                        (imageType, str(customKwargs)))
            imageGenerators = chain(
                imageGenerators,
                getattr(imageoperations, 'get%sImage' % imageType)(image, mask,
                                                                   **args))

        logger.debug('Extracting features')
        # Calculate features for all (filtered) images in the generator
        for inputImage, imageTypeName, inputKwargs in imageGenerators:
            logger.info('Calculating features for %s image', imageTypeName)
            inputImage, inputMask = imageoperations.cropToTumorMask(
                inputImage, mask, boundingBox, padDistance=kernelRadius)
            featureVector.update(
                self.computeFeatures(inputImage, inputMask, imageTypeName,
                                     **inputKwargs))

        logger.debug('Features extracted')

        return featureVector
Beispiel #15
0
            labelout = sitk.GetImageFromArray(labelimgnew)
            labelout.SetSpacing(bMainimgnii.GetSpacing())
            labelout.SetOrigin(bMainimgnii.GetOrigin())
            sitk.WriteImage(labelout, ImgDir + 'label.nii')
            print("label error:" + patient)
            labelnii = sitk.ReadImage(ImgDir + 'label.nii')
            features[DWI] = extractor.execute(bMainimgnii, labelnii)
        print(str(DWI) + ' calculate params features done.\n')
        #对每一幅图片加入小波变换的特征
        waveletFeatures = {}
        bb, correctedMask = imageoperations.checkMask(bMainimgnii,
                                                      labelnii,
                                                      LABEL=1)
        for decompositionImage, decompositionName, inputSettings in imageoperations.getWaveletImage(
                bMainimgnii, labelnii):
            decompositionImage, croppedMask = imageoperations.cropToTumorMask(
                decompositionImage, labelnii, bb)
            waveletFirstOrderFeatures = firstorder.RadiomicsFirstOrder(
                decompositionImage, croppedMask, **inputSettings)
            waveletFirstOrderFeatures.enableAllFeatures()
            print('Calculate firstorder features with ', decompositionName)
            waveletFeatures[
                decompositionName] = waveletFirstOrderFeatures.execute()
        wavelet[DWI] = waveletFeatures
    objects[patient] = (features, wavelet)
    # 至此,objects应该包含92个病人,每个病人共有features=100, waveletFeatures =8*18=144, 理论上一共提取了3X(100+144)=732个特征
OtherDir = './featuresselection/'
joblib.dump(objects, OtherDir + "brain_paramwave_features.pkl")
features_names = list(
    sorted(filter(lambda k: k.startswith("original_"), features[0])))

# Can be enabled by setting 'resampledPixelSpacing' to a list of 3 floats (new voxel size in mm for x, y and z)
kwargs = {'binWidth': 25,
          'interpolator': sitk.sitkBSpline,
          'resampledPixelSpacing': None}

#
# If enabled, resample image (resampled image is automatically cropped.
# If resampling is not enabled, crop image instead
#
if kwargs['interpolator'] is not None and kwargs['resampledPixelSpacing'] is not None:
  image, mask = imageoperations.resampleImage(image, mask, kwargs['resampledPixelSpacing'], kwargs['interpolator'])
else:
  bb, correctedMask = imageoperations.checkMask(image, mask)
  if correctedMask is not None:
    mask = correctedMask
  image, mask = imageoperations.cropToTumorMask(image, mask, bb)

#
# Show the first order feature calculations
#
firstOrderFeatures = firstorder.RadiomicsFirstOrder(image, mask, **kwargs)

firstOrderFeatures.enableFeatureByName('Mean', True)
# firstOrderFeatures.enableAllFeatures()

print('Will calculate the following first order features: ')
for f in firstOrderFeatures.enabledFeatures.keys():
  print('  ', f)
  print(getattr(firstOrderFeatures, 'get%sFeatureValue' % f).__doc__)

print('Calculating first order features...')
Beispiel #17
0
def calc_radio_fea(img: np.ndarray, mask: np.ndarray) -> List[np.ndarray]:
    assert type(
        img
    ) == np.ndarray, f"TypeError, expected np.ndarray but Got {type(img)}"
    assert img.shape == mask.shape, f"SizeError, expected to be same, but Got {img.shape} and {mask.shape}"

    image = sitk.GetImageFromArray(img)
    mask = sitk.GetImageFromArray(mask)

    # Setting for the feature calculation.
    # Currently, resampling is disabled.
    # Can be enabled by setting 'resampledPixelSpacing' to a list of 3 floats (new voxel size in mm for x, y and z)
    settings = {
        'binWidth': 25,
        'interpolator': sitk.sitkBSpline,
        'resampledPixelSpacing': None
    }
    #
    # If enabled, resample image (resampled image is automatically cropped.
    #
    interpolator = settings.get('interpolator')
    resampledPixelSpacing = settings.get('resampledPixelSpacing')
    if interpolator is not None and resampledPixelSpacing is not None:
        image, mask = imageoperations.resampleImage(image, mask, **settings)

    bb, correctedMask = imageoperations.checkMask(image, mask)
    if correctedMask is not None:
        mask = correctedMask
    image, mask = imageoperations.cropToTumorMask(image, mask, bb)
    results_collect = dict()
    results_np = list()
    # Fisrt order
    firstOrderFeatures = firstorder.RadiomicsFirstOrder(
        image, mask, **settings)
    # firstOrderFeatures.enableFeatureByName('Mean', True)
    firstOrderFeatures.enableAllFeatures()
    results: dict = firstOrderFeatures.execute()  # dict()
    # results_collect['FirstOrder'] = results
    results_np.append(np.array([value for key, value in results.items()]))
    #
    shapeFeatures = shape.RadiomicsShape(image, mask, **settings)
    shapeFeatures.enableAllFeatures()
    results = shapeFeatures.execute()
    # results_collect['ShapeFeature'] = results
    results_np.append(np.array([value for key, value in results.items()]))
    ###
    glcmFeatures = glcm.RadiomicsGLCM(image, mask, **settings)
    glcmFeatures.enableAllFeatures()
    results = glcmFeatures.execute()
    # results_collect['GLCM'] = results
    results_np.append(np.array([value for key, value in results.items()]))
    ###
    glrlmFeatures = glrlm.RadiomicsGLRLM(image, mask, **settings)
    glrlmFeatures.enableAllFeatures()
    results = glrlmFeatures.execute()
    # results_collect['GLRLM'] = results
    results_np.append(np.array([value for key, value in results.items()]))
    ###
    glszmFeatures = glszm.RadiomicsGLSZM(image, mask, **settings)
    glszmFeatures.enableAllFeatures()
    results = glszmFeatures.execute()
    # results_collect['GLSZM'] = results
    results_np.append(np.array([value for key, value in results.items()]))

    gldmFeatures = gldm.RadiomicsGLDM(image, mask, **settings)
    gldmFeatures.enableAllFeatures()
    results = gldmFeatures.execute()
    results_np.append(np.array([value for key, value in results.items()]))

    return results_np
    def execute(self, imageFilepath, maskFilepath, label=None):
        """
    Compute radiomics signature for provide image and mask combination.
    First, image and mask are loaded and normalized/resampled if necessary. Second, if enabled, provenance information
    is calculated and stored as part of the result. Next, shape features are calculated on a cropped (no padding)
    version of the original image. Then other featureclasses are calculated using all specified input images in
    ``inputImages``. Images are cropped to tumor mask (no padding) after application of any filter and before being
    passed to the feature class. Finally, the dictionary containing all calculated features is returned.

    :param imageFilepath: SimpleITK Image, or string pointing to image file location
    :param maskFilepath: SimpleITK Image, or string pointing to labelmap file location
    :param label: Integer, value of the label for which to extract features. If not specified, last specified label
        is used. Default label is 1.
    :returns: dictionary containing calculated signature ("<filter>_<featureClass>_<featureName>":value).
    """
        # Enable or disable C extensions for high performance matrix calculation. Only logs a message (INFO) when setting is
        # successfully changed. If an error occurs, full-python mode is forced and a warning is logged.
        radiomics.enableCExtensions(self.kwargs['enableCExtensions'])

        if label is not None:
            self.kwargs.update({'label': label})

        self.logger.info('Calculating features with label: %d',
                         self.kwargs['label'])
        self.logger.debug('Enabled input images types: %s', self.inputImages)
        self.logger.debug('Enabled features: %s', self.enabledFeatures)
        self.logger.debug('Current settings: %s', self.kwargs)

        featureVector = collections.OrderedDict()
        image, mask = self.loadImage(imageFilepath, maskFilepath)

        if image is None or mask is None:
            # No features can be extracted, return the empty featureVector
            return featureVector

        # Check whether loaded mask contains a valid ROI for feature extraction
        boundingBox = imageoperations.checkMask(image, mask, **self.kwargs)

        if boundingBox is None:
            # Mask checks failed, do not extract features and return the empty featureVector
            return featureVector

        self.logger.debug(
            'Image and Mask loaded and valid, starting extraction')

        if self.kwargs['additionalInfo']:
            featureVector.update(
                self.getProvenance(imageFilepath, maskFilepath, mask))

        # If shape should be calculation, handle it separately here
        if 'shape' in self.enabledFeatures.keys():
            croppedImage, croppedMask = imageoperations.cropToTumorMask(
                image, mask, boundingBox, self.kwargs['label'])
            enabledFeatures = self.enabledFeatures['shape']

            self.logger.info('Computing shape')
            shapeClass = self.featureClasses['shape'](croppedImage,
                                                      croppedMask,
                                                      **self.kwargs)
            if enabledFeatures is None or len(enabledFeatures) == 0:
                shapeClass.enableAllFeatures()
            else:
                for feature in enabledFeatures:
                    shapeClass.enableFeatureByName(feature)

            shapeClass.calculateFeatures()
            for (featureName,
                 featureValue) in six.iteritems(shapeClass.featureValues):
                newFeatureName = 'original_shape_%s' % (featureName)
                featureVector[newFeatureName] = featureValue

        # Make generators for all enabled input image types
        self.logger.debug('Creating input image type iterator')
        imageGenerators = []
        for imageType, customKwargs in six.iteritems(self.inputImages):
            args = self.kwargs.copy()
            args.update(customKwargs)
            self.logger.info('Adding image type "%s" with settings: %s' %
                             (imageType, str(args)))
            imageGenerators = chain(
                imageGenerators,
                getattr(imageoperations, 'get%sImage' % imageType)(image,
                                                                   **args))

        self.logger.debug('Extracting features')
        # Calculate features for all (filtered) images in the generator
        for inputImage, inputImageName, inputKwargs in imageGenerators:
            self.logger.info('Calculating features for %s image',
                             inputImageName)
            inputImage, inputMask = imageoperations.cropToTumorMask(
                inputImage, mask, boundingBox, self.kwargs['label'])
            featureVector.update(
                self.computeFeatures(inputImage, inputMask, inputImageName,
                                     **inputKwargs))

        self.logger.debug('Features extracted')

        return featureVector
Beispiel #19
0
    def execute(self, imageFilepath, maskFilepath, label=None):
        """
    Compute radiomics signature for provide image and mask combination.
    First, image and mask are loaded and normalized/resampled if necessary. Second, if enabled, provenance information
    is calculated and stored as part of the result. Next, shape features are calculated on a cropped (no padding)
    version of the original image. Then other featureclasses are calculated using all specified input images in
    ``inputImages``. Images are cropped to tumor mask (no padding) after application of any filter and before being
    passed to the feature class. Finally, the dictionary containing all calculated features is returned.

    :param imageFilepath: SimpleITK Image, or string pointing to image file location
    :param maskFilepath: SimpleITK Image, or string pointing to labelmap file location
    :param label: Integer, value of the label for which to extract features. If not specified, last specified label
        is used. Default label is 1.
    :returns: dictionary containing calculated signature ("<filter>_<featureClass>_<featureName>":value).
    """
        # Enable or disable C extensions for high performance matrix calculation. Only logs a message (INFO) when setting is
        # successfully changed. If an error occurs, full-python mode is forced and a warning is logged.
        radiomics.enableCExtensions(self.kwargs['enableCExtensions'])

        if label is not None:
            self.kwargs.update({'label': label})

        self.logger.info('Calculating features with label: %d',
                         self.kwargs['label'])

        featureVector = collections.OrderedDict()
        image, mask = self.loadImage(imageFilepath, maskFilepath)

        if image is not None and mask is not None:
            if self.kwargs['additionalInfo']:
                featureVector.update(
                    self.getProvenance(imageFilepath, maskFilepath, mask))

            # Bounding box only needs to be calculated once after resampling, store the value, so it doesn't get calculated
            # after every filter
            boundingBox = None

            # If shape should be calculation, handle it separately here
            if 'shape' in self.enabledFeatures.keys():
                croppedImage, croppedMask, boundingBox = \
                  imageoperations.cropToTumorMask(image, mask, self.kwargs['label'], boundingBox)
                enabledFeatures = self.enabledFeatures['shape']
                shapeClass = self.featureClasses['shape'](croppedImage,
                                                          croppedMask,
                                                          **self.kwargs)
                if enabledFeatures is None or len(enabledFeatures) == 0:
                    shapeClass.enableAllFeatures()
                else:
                    for feature in enabledFeatures:
                        shapeClass.enableFeatureByName(feature)

                if self.kwargs['verbose']: print("\t\tComputing shape")
                shapeClass.calculateFeatures()
                for (featureName,
                     featureValue) in six.iteritems(shapeClass.featureValues):
                    newFeatureName = "original_shape_%s" % (featureName)
                    featureVector[newFeatureName] = featureValue

            # Make generators for all enabled input image types
            imageGenerators = []
            for imageType, customKwargs in six.iteritems(self.inputImages):
                args = self.kwargs.copy()
                args.update(customKwargs)
                self.logger.info("Applying filter: '%s' with settings: %s" %
                                 (imageType, str(args)))
                imageGenerators = chain(
                    imageGenerators,
                    eval('imageoperations.get%sImage(image, **args)' %
                         (imageType)))

            # Calculate features for all (filtered) images in the generator
            for inputImage, inputImageName, inputKwargs in imageGenerators:
                inputImage, inputMask, boundingBox = \
                  imageoperations.cropToTumorMask(inputImage, mask, self.kwargs['label'], boundingBox)
                featureVector.update(
                    self.computeFeatures(inputImage, inputMask, inputImageName,
                                         **inputKwargs))

        return featureVector