Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature extractions on 2D tiff images #339

Closed
linnsog opened this issue Jan 22, 2018 · 17 comments
Closed

Feature extractions on 2D tiff images #339

linnsog opened this issue Jan 22, 2018 · 17 comments

Comments

@linnsog
Copy link

linnsog commented Jan 22, 2018

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

@JoostJM
Copy link
Collaborator

JoostJM commented Jan 22, 2018

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
Copy link
Author

linnsog commented Jan 22, 2018

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
Copy link
Collaborator

JoostJM commented Jan 22, 2018

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
Copy link
Author

linnsog commented Jan 22, 2018 via email

@JoostJM
Copy link
Collaborator

JoostJM commented Jan 22, 2018

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
Copy link
Collaborator

JoostJM commented Jan 22, 2018

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
Copy link
Author

linnsog commented Jan 22, 2018 via email

@QiChen2014
Copy link

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

@JoostJM
Copy link
Collaborator

JoostJM commented Mar 16, 2018

@QiChen2014 Yes

@destinybuilt
Copy link

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
Copy link
Collaborator

JoostJM commented May 29, 2019

@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
Copy link

@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
Copy link
Collaborator

JoostJM commented May 29, 2019

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
Copy link

@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
Copy link
Collaborator

JoostJM commented May 29, 2019

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
Copy link

destinybuilt commented May 30, 2019

@JoostJM It still reports an error....

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants