-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Tidy code and add LICENSE and README.md.
- Loading branch information
1 parent
870f845
commit 310476c
Showing
17 changed files
with
1,015 additions
and
158 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -102,3 +102,6 @@ ENV/ | |
|
||
# Ignore featurization folder | ||
/featurization/ | ||
|
||
# Ignore visualization folder | ||
/visualization/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
Lung Cancer Action Toolkit (LCAT) | ||
================================= | ||
The Lung Cancer Action Toolkit (LCAT) provides tools for loading and analyzing chest CT scans. | ||
|
||
The Lung Cancer Action Toolkit includes functionality for: | ||
* Loading LIDC-IDRI data, including | ||
* Voxel data, optionally normalizing pixel distances | ||
* Nodule data, including radiologist segmentations and radiologist-assigned characteristics | ||
* Performing segmentation of the lung from chest CTs | ||
* Performing segmentation of the body from chest CTs | ||
* Analyzing mouth-to-point distances through the trachea and the bronchioles of the lungs | ||
* Analyzing nodule size and shape | ||
|
||
Detailed documentation for each of these functions is available at TODO. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
""" | ||
Body depth featurization module. | ||
""" | ||
from __future__ import absolute_import | ||
|
||
import numpy as np | ||
import pandas as pd | ||
import scipy.ndimage | ||
|
||
import lcat | ||
from . import registry | ||
|
||
|
||
@registry.register_featurizer('body_depth') | ||
def featurize_center(scan): | ||
""" | ||
Featurize the given scan, returning body depth statistics. | ||
""" | ||
# Create data distance placeholder | ||
index = pd.Index([], name='nodule_id') | ||
data = pd.DataFrame(index=index, columns=['min_body_depth', | ||
'mean_body_depth', | ||
'median_body_depth', | ||
'max_body_depth']) | ||
|
||
# Get the body segmentation for the scan | ||
body_shell = lcat.get_body_segmentation(scan) | ||
|
||
# Get body depths | ||
body_depths = scipy.ndimage.distance_transform_edt(body_shell) | ||
|
||
# Multiply by unit lengths (assume cubic unit cell) | ||
body_depths *= np.mean(scan.unit_cell[0]) | ||
|
||
# For each nodule | ||
for nodule in scan.nodules: | ||
# Create full mask | ||
mask = lcat.util.get_full_nodule_mask(nodule, scan.voxels.shape) | ||
|
||
# Select tumor tracheal distances | ||
nodule_depths = body_depths[mask] | ||
|
||
# Add attributes to dataframe | ||
data.loc[nodule.nodule_id, :] = [ | ||
np.min(nodule_depths), | ||
np.mean(nodule_depths), | ||
np.median(nodule_depths), | ||
np.max(nodule_depths) | ||
] | ||
|
||
return data |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
""" | ||
Characteristics featurization module. | ||
""" | ||
from __future__ import absolute_import | ||
|
||
import numpy as np | ||
import pandas as pd | ||
import scipy.ndimage | ||
|
||
import lcat | ||
from . import registry | ||
|
||
|
||
CHARACTERISTICS = [ | ||
'subtlety', | ||
'internalStructure', | ||
'calcification', | ||
'sphericity', | ||
'margin', | ||
'lobulation', | ||
'spiculation', | ||
'texture', | ||
'malignancy', | ||
] | ||
|
||
|
||
@registry.register_featurizer('characteristics') | ||
def featurize_characteristics(scan): | ||
""" | ||
Featurize the given scan, returning nodule characteristics. | ||
""" | ||
# Create data distance placeholder | ||
index = pd.Index([], name='nodule_id') | ||
data = pd.DataFrame(index=index, columns=CHARACTERISTICS) | ||
|
||
# For each nodule | ||
for nodule in scan.nodules: | ||
# Load characteristics | ||
data.loc[nodule.nodule_id] = [nodule.characteristics.get(attribute, np.nan) | ||
for attribute in CHARACTERISTICS] | ||
|
||
return data |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
""" | ||
Segments a body from a CT scan. | ||
""" | ||
import numpy as np | ||
import skimage | ||
import skimage.measure | ||
import skimage.segmentation | ||
|
||
import lcat | ||
|
||
|
||
def get_body_segmentation(scan): | ||
""" | ||
Given a `Scan` object representing a chest CT scan, return a binary mask representing the region | ||
occupied by the body. | ||
""" | ||
# Threshold the image (threshold is lower limit for lung tissue in HU) | ||
foreground = scan.voxels >= -700 | ||
|
||
# Identify strongly connected components | ||
labels = skimage.measure.label(foreground, connectivity=1) | ||
|
||
# Identify the largest volume | ||
body_mask = get_largest_volume(labels) | ||
|
||
# Obtain body envelope | ||
envelope_mask = get_body_envelope(body_mask) | ||
|
||
return envelope_mask | ||
|
||
|
||
def get_largest_volume(labels): | ||
""" | ||
Return a binary mask equivalent to the component with the largest volumes in the array `labels`. | ||
""" | ||
return labels == get_top_value(labels[labels != 0]) | ||
|
||
|
||
def get_top_value(arr): | ||
""" | ||
Given an ndarray, return the value which occurs most frequently. | ||
""" | ||
# Count values | ||
values, counts = np.unique(arr, return_counts=True) | ||
|
||
# Identify top value | ||
top_value_index = np.argmax(counts) | ||
|
||
return values[top_value_index] | ||
|
||
|
||
def get_body_envelope(body_mask): | ||
""" | ||
Given a mask representing thresholded lung values, obtain an envelope containing the lung region | ||
with no interior holes. | ||
""" | ||
# Invert the mask | ||
reversed_mask = np.logical_not(body_mask) | ||
|
||
# Identify connected_components | ||
reversed_labels = skimage.measure.label(reversed_mask) | ||
|
||
# Identify inner labels only (on x and y edges, z outer labels can remain) | ||
inner_labels = lcat.util.clear_border(reversed_labels, axis=[0, 1]) | ||
|
||
# Obtain only the outermost (edge-touching) region | ||
outside_mask = np.logical_xor(reversed_labels != 0, inner_labels != 0) | ||
|
||
# Invert the mask again to get the envelope | ||
envelope_mask = np.logical_not(outside_mask) | ||
|
||
return envelope_mask |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
Oops, something went wrong.