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]
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
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
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()
def generateWaveletBaseline(): logger = logging.getLogger('radiomics.addBaseline') baselineFile = '../data/baseline/wavelet.npy' testCase = 'test_wavelet_64x64x64' if os.path.isfile( baselineFile): # Check if the baseline does not yet exist logger.info('Baseline already exists, cancelling generation') return baselineDict = {} wavelets = ('HHH', 'HHL', 'HLH', 'HLL', 'LHH', 'LHL', 'LLH', 'LLL') 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] im_arr = sitk.GetArrayFromImage(image) ma_arr = sitk.GetArrayFromImage( mask) == 1 # Convert to boolean array, label = 1 voxelArray = im_arr[ma_arr] # 1D array of all voxels inside mask baselineDict[level] = voxelArray baseline = [baselineDict[wl] for wl in wavelets] baseline = numpy.array(baseline) numpy.save(baselineFile, baseline)
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
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
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
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()
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
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
def setFeatureClassAndTestCase(self, className, test): """ 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. """ global TEST_CASES if self._featureClassName == className and self._test == test: return False self._test = test self._testedSet.add(self._test) # 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._baseline.keys( ) # Check if a baseline has been read for this class self._featureClassName = className # Check if test settings have changed if self._current_config != self._baseline[className].getTestConfig( test): self._current_config = self._baseline[className].getTestConfig( test) self._testCase = None # forces image to be reloaded (as settings have changed) # Next, set testCase if necessary if self._testCase != self._current_config['TestCase']: self._testCase = self._current_config['TestCase'] self._logger.info("Reading the image and mask for test case %s", self._testCase) assert self._current_config['TestCase'] in TEST_CASES imageName, maskName = getTestCase(self._testCase) assert imageName is not None assert maskName is not None self._image = sitk.ReadImage(imageName) self._mask = sitk.ReadImage(maskName) if 'ImageHash' in self._current_config: assert sitk.Hash( self._image) == self._current_config['ImageHash'] if 'MaskHash' in self._current_config: assert sitk.Hash( self._mask) == self._current_config['MaskHash'] settings = self._current_config.get('Settings', {}) interpolator = settings.get('interpolator', sitk.sitkBSpline) resampledPixelSpacing = settings.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, settings.get('label', 1), settings.get('padDistance', 5)) self._bb, correctedMask = imageoperations.checkMask( self._image, self._mask, **settings) if correctedMask is not None: self._mask = correctedMask self._imageType = None return True
s_l = np.shape(labelimg) labelimgnew = np.zeros((s_m[0], s_m[1], s_m[2]), int) for i in range(s_l[0]): labelimgnew[i] = labelimg[i] 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/'
# 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) 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)
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
# 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) 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 = imageoperations.checkMask(image, mask) 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__)
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 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