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
def execute(self, imageFilepath, maskFilepath, label=None): """ 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. 4. Shape features are calculated on a cropped (no padding) version of the original image. 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). """ # 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.settings['enableCExtensions']) if self.geometryTolerance != self.settings.get('geometryTolerance'): self._setTolerance() if label is not None: self.settings['label'] = label 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') # 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 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, 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
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