Skip to content

Commit

Permalink
Add a command line interface for collageradiomics. #86
Browse files Browse the repository at this point in the history
  • Loading branch information
nathanhillyer committed Sep 20, 2020
1 parent 8b59241 commit 07b1a7e
Show file tree
Hide file tree
Showing 2 changed files with 126 additions and 0 deletions.
93 changes: 93 additions & 0 deletions cli/collageradiomicscli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
#!usr/bin/env python
# -*- coding: utf-8 -*-

import click
import os
import sys
import SimpleITK as sitk
import csv
from scipy import stats
import numpy as np

import collageradiomics

@click.command()
@click.option('-i', '--input', required=True, help='Path to an input image from which features will be extracted.')
@click.option('-m', '--mask', required=True, help='Path to a mask that will be considered as binary. The highest pixel value will be considered as information and all other values will be considered outside the mask')
@click.option('-o', '--outputfile', required=True, help='Path to the output CSV file.')
@click.option('-v', '--verbose', default=True, help='Provides additional debug output.')
@click.option('-d', '--dimensions', help='Optional number of dimensions upon which to run collage. Supported values are 2 and 3. If left out, we will default to the dimensionality of the image itself, which may not reflect expected behavior if the image has an alpha channel.', type=click.IntRange(2, 3, clamp=True))
@click.option('-s', '--svdradius', default=5, help='SVD radius is used for the dominant angle calculation pixel radius. DEFAULTS to 5 and is suggested to remain at the default.')
@click.option('-h', '--haralickwindow', default=-1, help='Number of pixels around each pixel used to calculate the haralick texture. DEFAULTS to svdradius * 2 - 1.')
@click.option('-b', '--binsize', default=64, help='Number of bins to use while calculating the grey level cooccurence matrix. DEFAULTS to 64.')
def run(input, mask, outputfile, verbose, dimensions, svdradius, haralickwindow, binsize):
"""CoLlAGe captures subtle anisotropic differences in disease pathologies by measuring entropy of co-occurrences of voxel-level gradient orientations on imaging computed within a local neighborhood."""

image = sitk.ReadImage(input)
mask = sitk.ReadImage(mask)

image_array = sitk.GetArrayFromImage(image)
mask_array = sitk.GetArrayFromImage(mask)

# Remove any extra array dimensions if the user explicitly asks for 2D.
if dimensions == 2:
image_array = image_array[:,:,0]
mask_array = mask_array [:,:,0]

collage = collageradiomics.Collage(
image_array,
mask_array,
svd_radius=svdradius,
verbose_logging=verbose,
num_unique_angles=binsize)

collage.execute()

# Create a csv file at the passed in output file location.
with open(outputfile, 'w', newline='') as csv_output_file:
writer = csv.writer(csv_output_file)

# Write the columns.
writer.writerow(['FeatureName', 'Value'])
for feature in collageradiomics.HaralickFeature:
feature_output = collage.get_single_feature_output(feature)
if image_array.ndim == 2:
feature_output = feature_output[~np.isnan(feature_output)]

# NumPy supports median natively, we'll use that.
median = np.nanmedian(feature_output, axis=None)

# Use SciPy for kurtosis, variance, and skewness.
feature_stats = stats.describe(feature_output, axis=None)

# Write CSV row for current feature.
_write_csv_stats_row(writer, feature, median, feature_stats.skewness, feature_stats.kurtosis, feature_stats.variance)
else:
# Extract phi and theta angles.
feature_output_theta = feature_output[:,:,:,0]
feature_output_phi = feature_output[:,:,:,1]

# Remove NaN for stat calculations.
feature_output_theta = feature_output_theta[~np.isnan(feature_output_theta)]
feature_output_phi = feature_output_phi[~np.isnan(feature_output_phi)]

# NumPy supports median natively, we'll use that.
median_theta = np.nanmedian(feature_output_theta, axis=None)
median_phi = np.nanmedian(feature_output_phi, axis=None)

# Use SciPy for kurtosis, variance, and skewness.
feature_stats_theta = stats.describe(feature_output_theta.flatten(), axis=None)
feature_stats_phi = stats.describe(feature_output_phi.flatten(), axis=None)

# Write CSV rows for each angle.
_write_csv_stats_row(writer, feature, median_theta, feature_stats_theta.skewness, feature_stats_theta.kurtosis, feature_stats_theta.variance, 'Theta')
_write_csv_stats_row(writer, feature, median_phi, feature_stats_phi.skewness, feature_stats_phi.kurtosis, feature_stats_phi.variance, 'Phi')

def _write_csv_stats_row(writer, feature, median, skewness, kurtosis, variance, suffix=''):
writer.writerow([f'Collage{feature.name}Median{suffix}', f'{median:.10f}'])
writer.writerow([f'Collage{feature.name}Skewness{suffix}', f'{skewness:.10f}'])
writer.writerow([f'Collage{feature.name}Kurtosis{suffix}', f'{kurtosis:.10f}'])
writer.writerow([f'Collage{feature.name}Variance{suffix}', f'{variance:.10f}'])

if __name__ == '__main__':
run()
33 changes: 33 additions & 0 deletions cli/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from setuptools import setup

setup(name='collageradiomicscli',
version='1.0.0',
description='Get Collage features from an image and a binary mask',
url='https://github.com/radxtools/collageradiomics',
python_requires='>=3.6',
author='Toth Technology',
author_email='toth-tech@hillyer.me',
license='BSD-3-Clause',
zip_safe=False,
install_requires=[
'collageradiomics',
'setuptools>=47',
'SimpleITK==1.2.4',
'click'
],
scripts=['collageradiomicscli.py'],
classifiers=[
'Intended Audience :: Science/Research',
'Intended Audience :: Developers',
'License :: OSI Approved :: BSD License',
'Operating System :: OS Independent',
'Operating System :: POSIX :: Linux',
'Operating System :: Microsoft :: Windows :: Windows 10',
'Operating System :: MacOS',
'Programming Language :: Python :: 3',
'Topic :: Scientific/Engineering :: Bio-Informatics',
]
)

0 comments on commit 07b1a7e

Please sign in to comment.