AIM-Harvard / pyradiomics

Open-source python package for the extraction of Radiomics features from 2D and 3D images and binary masks. Support: https://discourse.slicer.org/c/community/radiomics
http://pyradiomics.readthedocs.io/
BSD 3-Clause "New" or "Revised" License
1.18k stars 497 forks source link

Feature extractions on 2D tiff images #339

Closed linnsog closed 6 years ago

linnsog commented 6 years ago

Is it possible to use extract first order features on 2D tiff images using the built in class?

JoostJM commented 6 years ago

You need to convert your tiff image to a 3D volume (and make it gray scale). See also this thread on the PyRadiomics forum and issue #298 here on the github.

linnsog commented 6 years ago

The images are already gray scale. Do they need to be converted to nrrd files, and does the mask also need to be in 3d?

JoostJM commented 6 years ago

Yes and yes. Image and Mask must both be 3D (but when converting from 2D, they will have a size of 1 in the 3rd dimension).

You can do this using SimpleITK.JoinSeries(), but check that the images are really grayscale (i.e. not a vector for each pixel value, check this by print(im), where im is a SimpleITK image object).

Related: what is the origin of your tiff images? If the origin is DICOM, it is better to directly convert DICOM to NRRD (as in tiff, spacing information etc. is often discarded).

linnsog commented 6 years ago

The images are original in tif. We made the mask from numpy.ones(), and made it the same size as the image (since the whole image is of interest). Then added a new axis to make it 3d and made a nrrd-file. We also converted the tif image to 3d with "image3d = sitk.JoinSeries(image2d)", but we are having problems to make it in to a nrrd file so we can us it as input in the RadiomicsFirstOrder class.

2018-01-22 12:07 GMT+01:00 Joost van Griethuysen notifications@github.com:

Yes and yes. Image and Mask must both be 3D (but when converting from 2D, they will have a size of 1 in the 3rd dimension).

You can do this using SimpleITK.JoinSeries(), but check that the images are really grayscale (i.e. not a vector for each pixel value, check this by print(im), where im is a SimpleITK image object).

Related: what is the origin of your tiff images? If the origin is DICOM, it is better to directly convert DICOM to NRRD (as in tiff, spacing information etc. is often discarded).

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/Radiomics/pyradiomics/issues/339#issuecomment-359392204, or mute the thread https://github.com/notifications/unsubscribe-auth/AV5s4FtOlCaX37e7WJULUztHCSzAO2AHks5tNGv5gaJpZM4Rmcep .

JoostJM commented 6 years ago

Try this:

import SimpleITK as sitk
import numpy
from radiomics import featureextractor

# Transform input
im_tiff = sitk.ReadImage('path/to/image.tiff')
im_vect = sitk.JoinSeries(im_tiff)  # Add 3rd dimension
im = sitk.VectorImageSelectionCast(im_vect, 0, sitk.sitkFloat64)  # Select first color channel (if image is grayscale, all channels are equal)

# Build full mask
im_arr = sitk.GetArrayFromImage(im)
ma_arr = numpy.ones(im_arr.shape)
ma = sitk.GetImageFromArray(ma_arr)
ma.CopyInformation(im)

# Instantiate extractor
extractor = featureextractor.RadiomicsFeaturesExtractor('path/to/config')  # optional supply parameter file)

# extractor = featureextractor.RadiomicsFeaturesExtractor()  # Default settings 

# Enable just first order
extractor.disableAllFeatures()
extractor.enableFeatureClassByName('firstorder')

# Extract features
results = extractor.execute(im, ma)
JoostJM commented 6 years ago

If you want to store it as a nrrd, run this as well:

sitk.WriteImage(im, 'path/to/image.nrrd') Don't forget the correct extension, SimpleITK uses this to determine the format.

linnsog commented 6 years ago

Looks like it's working now. Thanks a lot!

2018-01-22 14:51 GMT+01:00 Joost van Griethuysen notifications@github.com:

Try this:

import SimpleITK as sitk import numpy from radiomics import featureextractor

Transform input

im_tiff = sitk.ReadImage('path/to/image.tiff') im_vect = sitk.JoinSeries(im_tiff) # Add 3rd dimension im = sitk.VectorImageSelectionCast(im_vect, 0, sitk.sitkFloat64) # Select first color channel (if image is grayscale, all channels are equal)

Build full mask

im_arr = sitk.GetArrayFromImage(im) ma_arr = numpy.ones(im_arr.shape) ma = sitk.GetImageFromArray(ma_arr) ma.CopyInformation(im)

Instantiate extractor

extractor = featureextractor.RadiomicsFeaturesExtractor('path/to/config') # optional supply parameter file)

extractor = featureextractor.RadiomicsFeaturesExtractor() # Default settings

Enable just first order

extractor.disableAllFeatures extractor.enableFeatureClassByName('firstorder')

Extract features

results = extractor.execute(im, ma)

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/Radiomics/pyradiomics/issues/339#issuecomment-359428638, or mute the thread https://github.com/notifications/unsubscribe-auth/AV5s4CjqSQn1t-fHYKI53dBCas_poa6Wks5tNJJIgaJpZM4Rmcep .

QiChen2014 commented 6 years ago

@JoostJM I have a similar question , whether the DCM image (2D) also needs to convert to 3D?

JoostJM commented 6 years ago

@QiChen2014 Yes

destinybuilt commented 5 years ago

Try this:

import SimpleITK as sitk
import numpy
from radiomics import featureextractor

# Transform input
im_tiff = sitk.ReadImage('path/to/image.tiff')
im_vect = sitk.JoinSeries(im_tiff)  # Add 3rd dimension
im = sitk.VectorImageSelectionCast(im_vect, 0, sitk.sitkFloat64)  # Select first color channel (if image is grayscale, all channels are equal)

# Build full mask
im_arr = sitk.GetArrayFromImage(im)
ma_arr = numpy.ones(im_arr.shape)
ma = sitk.GetImageFromArray(ma_arr)
ma.CopyInformation(im)

# Instantiate extractor
extractor = featureextractor.RadiomicsFeaturesExtractor('path/to/config')  # optional supply parameter file)

# extractor = featureextractor.RadiomicsFeaturesExtractor()  # Default settings 

# Enable just first order
extractor.disableAllFeatures()
extractor.enableFeatureClassByName('firstorder')

# Extract features
results = extractor.execute(im, ma)

How to convert tif image (RGB) into grayscale image?

JoostJM commented 5 years ago

@destinybuilt, PyRadiomics has since been upgraded and now also supports 2D input. Therefore, the addition of the 3rd dimension is no longer necessary.

However, PyRadiomics does still require a scalar datatype. Your example constitutes one way of dealing with this (extracting features for channels separately). You can also put this in a loop to extract for all channels:

import SimpleITK as sitk
import numpy
from radiomics import featureextractor

# Transform input
im_vect = sitk.ReadImage('path/to/image.tiff')
# im_vect = sitk.JoinSeries(im_vect)  # Add 3rd dimension, NO LONGER NECESSARY

# Build full mask
im_size = numpy.array(im_vect.GetSize())[::-1]  # flip x, y, z to z, y, x
ma_arr = numpy.ones(im_size)
ma = sitk.GetImageFromArray(ma_arr)
ma.CopyInformation(im_vect)

# Instantiate extractor
extractor = featureextractor.RadiomicsFeaturesExtractor('path/to/config')  # optional supply parameter file)

# extractor = featureextractor.RadiomicsFeaturesExtractor()  # Default settings 

# Enable just first order
extractor.disableAllFeatures()
extractor.enableFeatureClassByName('firstorder')

# Extract features
results = []
selector = sitk.VectorIndexSelectionCastImageFilter()
for i in range(im_vect.GetNumberOfComponentsPerPixel()):
  selector.SetIndex(i)
  im = selector.Execute(im_vect)  # Select first color channel (if image is grayscale, all channels are equal)
  channel_result = extractor.execute(im, ma)
  channel_result["channel"] = i
  results.append(channel_result

Alternatively, you can merge the channels prior to extraction (yielding a scalar volume). For this, you'd need to decide how to aggregate the information from the separate channels. E.g. take the mean, max, min...

destinybuilt commented 5 years ago

@destinybuilt, PyRadiomics has since been upgraded and now also supports 2D input. Therefore, the addition of the 3rd dimension is no longer necessary.

However, PyRadiomics does still require a scalar datatype. Your example constitutes one way of dealing with this (extracting features for channels separately). You can also put this in a loop to extract for all channels:

import SimpleITK as sitk
import numpy
from radiomics import featureextractor

# Transform input
im_vect = sitk.ReadImage('path/to/image.tiff')
# im_vect = sitk.JoinSeries(im_vect)  # Add 3rd dimension, NO LONGER NECESSARY

# Build full mask
im_size = numpy.array(im_vect.GetSize())[::-1]  # flip x, y, z to z, y, x
ma_arr = numpy.ones(im_size)
ma = sitk.GetImageFromArray(ma_arr)
ma.CopyInformation(im_vect)

# Instantiate extractor
extractor = featureextractor.RadiomicsFeaturesExtractor('path/to/config')  # optional supply parameter file)

# extractor = featureextractor.RadiomicsFeaturesExtractor()  # Default settings 

# Enable just first order
extractor.disableAllFeatures()
extractor.enableFeatureClassByName('firstorder')

# Extract features
results = []
selector = sitk.VectorIndexSelectionCastImageFilter()
for i in range(im_vect.GetNumberOfComponentsPerPixel()):
  selector.SetIndex(i)
  im = selector.Execute(im_vect)  # Select first color channel (if image is grayscale, all channels are equal)
  channel_result = extractor.execute(im, ma)
  channel_result["channel"] = i
  results.append(channel_result

Alternatively, you can merge the channels prior to extraction (yielding a scalar volume). For this, you'd need to decide how to aggregate the information from the separate channels. E.g. take the mean, max, min...

Thank you for your reply! Maybe I should explain to you the purpose of applying pyradiomics to tif images: I want to extract all the features of the tif image...not just the first-order features; I use matlab to read the tif file (info = Tiff('path /to/image.tif', 'r');), read my image and mask (I already have a mask file so I don't need numpy to generate the mask) are 2D RGB files; see this post Previous content, so I thought I would like to increase the third dimension of my two tif files (set to 1), and I found that after using the sitk.JoinSeries function, the third dimension of my original tif file did not Was changed (I have never used simple itk and pyradiomics, so I guess maybe I don't know how to save the changed file overwrite to the original path...) The following is the code I used to run pyradiomics for extraction. Can you help to modify the code to meet the purpose of extracting all the features of the tif file (about twenty patients)? thank you very much!

import radiomics import radiomics.featureextractor as FEE import SimpleITK as sitk

file name

main_path = r'D:\1Transition\1Trans\Test' ori_name = r'\tissueSlide.tif' lab_name = r'\mask.tif' para_name = r'\Params.yaml'

File path

ori_path = main_path + ori_name # image path lab_path = main_path + lab_name # mask path para_path = main_path + para_name print("originl path: " + ori_path) print("label path: " + lab_path) print("parameter path: " + para_path)

Initialize the feature extractor using a configuration file

extractor = FEE.RadiomicsFeaturesExtractor(para_path) print("Extraction parameters:\n\t", extractor.settings) print("Enabled filters:\n\t", extractor._enabledImagetypes) print("Enabled features:\n\t", extractor._enabledFeatures)

Extract features

result = extractor.execute(ori_path, lab_path) # Extract features print("Result type:", type(result)) # result is returned in a Python ordered dictionary print("") print("Calculated features") for key, value in result.items(): # Output features print("\t", key, ":", value)

JoostJM commented 5 years ago

You can apply the following code, it will extract features for each channel in the image, and use the first channel in mask to get the mask (I assume all 3 channels in the mask are identical). If your image is grayscale, you can extract from 1 channel (e.g. channel 0), as all channels are the same.

Finally, I'd advise using a parameter file. The documentation for this is here.

You need a list of python tuples, which each tuple consisting of 3 values: p_name (identifier for your case), string pointing to the location of the image file and string pointing to the location of the mask file.

e.g.:

get_patients = [
  ('case_1', r'path\to\case_1\image.tiff', r'path\to\case_1\mask.tiff'),
  ('case_2', r'path\to\case_2\image.tiff', r'path\to\case_2\mask.tiff'),
  ...,
  ('case_n', r'path\to\case_n\image.tiff', r'path\to\case_n\mask.tiff'),
]

You can then use this piece of code:

import SimpleITK as sitk
import numpy
from radiomics import featureextractor

param_file = r'path\to\config.yml'

# Instantiate extractor
extractor = featureextractor.RadiomicsFeaturesExtractor('path/to/config')  # optional supply parameter file)

results_dict = {}

# Extract features for each case
for p_name, im_path, ma_path in get_patients:
  # Transform input
  im_vect = sitk.ReadImage(im_path)

  # Extract features
  results = []
  selector = sitk.VectorIndexSelectionCastImageFilter()
  for i in range(im_vect.GetNumberOfComponentsPerPixel()):  # extract features for each channel
    selector.SetIndex(i)
    im = selector.Execute(im_vect)  # Select first color channel (if image is grayscale, all channels are equal)
    channel_result = extractor.execute(im, ma_path, label_channel=0)  # label_channel = 0 selects the first channel in the mask tiff image
    channel_result["channel"] = i
    results.append(channel_result
  results_dict[p_name] = results

This will give you a python dictionary (results_dict), with 1 entry for each case. each entry value is a list (length = number of channels), where each item in the list is another python dictionary, giving you the feature values for that case for that channel:

results_dict = {
  'case_1': [
    { 'feature_1': value, 'feature_2': value, ..., 'feature_n': value},
    { 'feature_1': value, 'feature_2': value, ..., 'feature_n': value},
    { 'feature_1': value, 'feature_2': value, ..., 'feature_n': value}
  ],

  'case_2': [
    { 'feature_1': value, 'feature_2': value, ..., 'feature_n': value},
    { 'feature_1': value, 'feature_2': value, ..., 'feature_n': value},
    { 'feature_1': value, 'feature_2': value, ..., 'feature_n': value}
  ],

  ...,

  'case_n': [
    { 'feature_1': value, 'feature_2': value, ..., 'feature_n': value},
    { 'feature_1': value, 'feature_2': value, ..., 'feature_n': value},
    { 'feature_1': value, 'feature_2': value, ..., 'feature_n': value}
  ]
}
destinybuilt commented 5 years ago

@JoostJM Thank you very much, I will run your code a bit, but it will still report an error. The error is: RuntimeError: Exception thrown in SimpleITK VectorIndexSelectionCastImageFilter_Execute: c:\d\vs14-win64-pkg\simpleitk\code\common\include\sitkDualMemberFunctionFactory.hxx:214: Sitk::ERROR: Pixel type: 8-bit unsigned integer is not supported in 2D byclass itk::simple::VectorIndexSelectionCastImageFilter Here is my code: import SimpleITK as sitk import numpy from radiomics import featureextractor

param_file = r'D:\1Transition\1Trans\Test\Params.yaml'

Instantiate extractor

extractor = featureextractor.RadiomicsFeaturesExtractor(r'D:\1Transition\1Trans\Test\Params.yaml') # optional supply parameter file)

get_patients=[('case_1',r'D:\1Transition\1Trans\Test\tissueSlide.tif',r'D:\1Transition\1Trans\Test\mask.tif'),('case_2',r'D:\1Transition\1Trans\Test\tissueSlide.tif',r'D:\1Transition\1Trans\Test\mask.tif')]

results_dict = {}

Extract features for each case

for p_name, im_path, ma_path in get_patients:

Transform input

im_vect = sitk.ReadImage(im_path)

Extract features

results = [] selector = sitk.VectorIndexSelectionCastImageFilter() for i in range(im_vect.GetNumberOfComponentsPerPixel()): # extract features for each channel selector.SetIndex(i) im = selector.Execute(im_vect) # Select first color channel (if image is grayscale, all channels are equal) channel_result = extractor.execute(im, ma_path, label_channel=0) # label_channel = 0 selects the first channel in the mask tiff image channel_result["channel"] = i results.append(channel_result) results_dict[p_name] = results

I can't upload more than 10MB files (even if I use zip files). Can you give me one of your emails? I can pass you a tif file and the corresponding mask so that you can debug it.

JoostJM commented 5 years ago

Try this

import SimpleITK as sitk
import numpy
from radiomics import featureextractor

param_file = r'path\to\config.yml'

# Instantiate extractor
extractor = featureextractor.RadiomicsFeaturesExtractor('path/to/config')  # optional supply parameter file)

results_dict = {}

# Extract features for each case
for p_name, im_path, ma_path in get_patients:
  # Transform input
  im_vect = sitk.ReadImage(im_path)

  # Extract features
  results = []
  if im_vect.GetNumberOfComponentsPerPixel() == 1:  # scalar image
    results.append(extractor.execute(im_vect, ma_path, label_channel=0)
  else:  # vector image
    selector = sitk.VectorIndexSelectionCastImageFilter()
    for i in range(im_vect.GetNumberOfComponentsPerPixel()):  # extract features for each channel
      selector.SetIndex(i)
      im = selector.Execute(im_vect)  # Select first color channel (if image is grayscale, all channels are equal)
      channel_result = extractor.execute(im, ma_path, label_channel=0)  # label_channel = 0 selects the first channel in the mask tiff image
      channel_result["channel"] = i
      results.append(channel_result
  results_dict[p_name] = results
destinybuilt commented 5 years ago

@JoostJM It still reports an error....

TypeError: execute() got an unexpected keyword argument 'label_channel

destinybuilt commented 5 years ago

@JoostJM The following is the image and mask I shared with Google Cloud Drive:

https://drive.google.com/file/d/1tEj9AwFzrh_j-rjQtXeb_7bf5HN8BP5U/view?usp=sharing https://drive.google.com/file/d/1uvrwiKjDuhiKWpLoHnO8wZsdLRZn2jOY/view?usp=sharing https://drive.google.com/file/d/1Xfycu5EXFezEq6DXVu1THHbPKh-Jnp9V/view?usp=sharing