示例#1
0
    def process_testcase(self, test, featureClassName):
        self.logger.debug('processing testCase = %s, featureClassName = %s',
                          test, featureClassName)

        self.testUtils.setFeatureClassAndTestCase(featureClassName, test)

        testImage = self.testUtils.getImage('original')
        testMask = self.testUtils.getMask('original')

        featureClass = self.featureClasses[featureClassName](
            testImage, testMask, **self.testUtils.getSettings())

        featureClass.enableAllFeatures()
        featureClass.execute()

        if "_calculateMatrix" in dir(featureClass):
            cMat = getattr(featureClass, 'P_%s' % featureClassName)
            if cMat is not None:
                numpy.save(
                    os.path.join(self.baselineDir,
                                 '%s_%s.npy' % (test, featureClassName)), cMat)

        imageTypeName = 'original'

        # Update versions to reflect which configuration generated the baseline
        self.generalInfo = generalinfo.GeneralInfo()

        versions = self.generalInfo.getGeneralInfo()
        self.new_baselines[featureClassName].configuration[test].update(
            versions)

        self.new_baselines[featureClassName].baseline[test] = {
            '%s_%s_%s' % (imageTypeName, featureClassName, key): val
            for key, val in six.iteritems(featureClass.featureValues)
        }
示例#2
0
  def getProvenance(self, imageFilepath, maskFilepath, mask):
    """
    Generates provenance information for reproducibility. Takes the original image & mask filepath, as well as the
    resampled mask which is passed to the feature classes. Returns a dictionary with keynames coded as
    "general_info_<item>". For more information on generated items, see :ref:`generalinfo<radiomics-generalinfo-label>`
    """
    self.logger.info('Adding additional extraction information')

    provenanceVector = collections.OrderedDict()
    generalinfoClass = generalinfo.GeneralInfo(imageFilepath, maskFilepath, mask, self.settings, self.inputImages)
    for k, v in six.iteritems(generalinfoClass.execute()):
      provenanceVector['general_info_%s' % k] = v
    return provenanceVector
示例#3
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
示例#4
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