-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* add tests * add `PATIENT_ID` as a variable * add masked `deepFCD` segmentations for tests * re-organize * add nvm version * update image tag
- Loading branch information
Showing
11 changed files
with
280 additions
and
12 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
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
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.
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,9 @@ | ||
#!/usr/bin/env bash | ||
set -e | ||
|
||
pushd "$(dirname "$0")" | ||
|
||
echo "Running all tests" | ||
python3 test_deepFCD.py $@ | ||
|
||
popd |
Binary file added
BIN
+368 KB
...ions/sub-00055/noel_deepFCD_dropoutMC/sub-00055_noel_deepFCD_dropoutMC_prob_mean_1.nii.gz
Binary file not shown.
Binary file added
BIN
+370 KB
...tions/sub-00055/noel_deepFCD_dropoutMC/sub-00055_noel_deepFCD_dropoutMC_prob_var_1.nii.gz
Binary file not shown.
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,150 @@ | ||
""" | ||
Test deepFCD.py | ||
nptest.assert_allclose | ||
self.assertEqual | ||
self.assertTrue | ||
""" | ||
|
||
import os | ||
import unittest | ||
from tempfile import mktemp | ||
|
||
import ants | ||
import numpy as np | ||
import numpy.testing as nptest | ||
|
||
from utils import compare_images | ||
|
||
|
||
params = {} | ||
if os.environ.get("CI_TESTING") is not None: | ||
params["CI_TESTING_PRED_DIR"] = os.environ.get("CI_TESTING_PRED_DIR") | ||
params["CI_TESTING_PATIENT_ID"] = os.environ.get("CI_TESTING_PATIENT_ID") | ||
else: | ||
params["CI_TESTING_PRED_DIR"] = "/host/hamlet/local_raid/data/ravnoor/sandbox/pytests" | ||
params["CI_TESTING_PATIENT_ID"] = "sub-00055" | ||
|
||
|
||
class TestModule_deepFCD(unittest.TestCase): | ||
|
||
def setUp(self): | ||
# load predictions from a previous validated run (known as ground-truth labels in this context) | ||
self.gt_deepMask = ants.image_read('segmentations/sub-00055/sub-00055_brain_mask_final.nii.gz').clone('unsigned int') | ||
self.gt_deepFCD_mean = ants.image_read('segmentations/sub-00055/noel_deepFCD_dropoutMC/sub-00055_noel_deepFCD_dropoutMC_prob_mean_1.nii.gz').clone('float') | ||
self.gt_deepFCD_var = ants.image_read('segmentations/sub-00055/noel_deepFCD_dropoutMC/sub-00055_noel_deepFCD_dropoutMC_prob_var_1.nii.gz').clone('float') | ||
|
||
pred_path = os.path.join(params["CI_TESTING_PRED_DIR"], params["CI_TESTING_PATIENT_ID"]) | ||
# load predicitions from the most recent run | ||
self.pred_deepMask = ants.image_read(pred_path + '/' + params["CI_TESTING_PATIENT_ID"] + '_brain_mask_final.nii.gz').clone('unsigned int') | ||
self.pred_deepFCD_mean = ants.image_read(pred_path + '/noel_deepFCD_dropoutMC/' + params["CI_TESTING_PATIENT_ID"] + '_noel_deepFCD_dropoutMC_prob_mean_1.nii.gz').clone('float') | ||
self.pred_deepFCD_var = ants.image_read(pred_path + '/noel_deepFCD_dropoutMC/' + params["CI_TESTING_PATIENT_ID"] + '_noel_deepFCD_dropoutMC_prob_var_1.nii.gz').clone('float') | ||
|
||
self.imgs = [self.pred_deepMask, self.pred_deepFCD_mean, self.pred_deepFCD_var] | ||
self.pixeltypes = ['unsigned char', 'unsigned int', 'float'] | ||
|
||
def tearDown(self): | ||
pass | ||
|
||
def test_image_header_info(self): | ||
# def image_header_info(filename): | ||
for img in self.imgs: | ||
img.set_spacing([6.9]*img.dimension) | ||
img.set_origin([3.6]*img.dimension) | ||
tmpfile = mktemp(suffix='.nii.gz') | ||
ants.image_write(img, tmpfile) | ||
|
||
info = ants.image_header_info(tmpfile) | ||
self.assertEqual(info['dimensions'], img.shape) | ||
nptest.assert_allclose(info['direction'], img.direction) | ||
self.assertEqual(info['nComponents'], img.components) | ||
self.assertEqual(info['nDimensions'], img.dimension) | ||
self.assertEqual(info['origin'], img.origin) | ||
self.assertEqual(info['pixeltype'], img.pixeltype) | ||
self.assertEqual(info['pixelclass'], 'vector' if img.has_components else 'scalar') | ||
self.assertEqual(info['spacing'], img.spacing) | ||
|
||
try: | ||
os.remove(tmpfile) | ||
except: | ||
pass | ||
|
||
# non-existent file | ||
with self.assertRaises(Exception): | ||
tmpfile = mktemp(suffix='.nii.gz') | ||
ants.image_header_info(tmpfile) | ||
|
||
|
||
def test_image_read_write(self): | ||
# def image_read(filename, dimension=None, pixeltype='float'): | ||
# def image_write(image, filename): | ||
|
||
# test scalar images | ||
for img in self.imgs: | ||
img = (img - img.min()) / (img.max() - img.min()) | ||
img = img * 255. | ||
img = img.clone('unsigned char') | ||
for ptype in self.pixeltypes: | ||
img = img.clone(ptype) | ||
tmpfile = mktemp(suffix='.nii.gz') | ||
ants.image_write(img, tmpfile) | ||
|
||
img2 = ants.image_read(tmpfile) | ||
self.assertTrue(ants.image_physical_space_consistency(img,img2)) | ||
self.assertEqual(img2.components, img.components) | ||
nptest.assert_allclose(img.numpy(), img2.numpy()) | ||
|
||
# unsupported ptype | ||
with self.assertRaises(Exception): | ||
ants.image_read(tmpfile, pixeltype='not-suppoted-ptype') | ||
|
||
# test saving/loading as npy | ||
for img in self.imgs: | ||
tmpfile = mktemp(suffix='.npy') | ||
ants.image_write(img, tmpfile) | ||
img2 = ants.image_read(tmpfile) | ||
|
||
self.assertTrue(ants.image_physical_space_consistency(img,img2)) | ||
self.assertEqual(img2.components, img.components) | ||
nptest.assert_allclose(img.numpy(), img2.numpy()) | ||
|
||
# with no json header | ||
arr = img.numpy() | ||
tmpfile = mktemp(suffix='.npy') | ||
np.save(tmpfile, arr) | ||
img2 = ants.image_read(tmpfile) | ||
nptest.assert_allclose(img.numpy(), img2.numpy()) | ||
|
||
# non-existant file | ||
with self.assertRaises(Exception): | ||
tmpfile = mktemp(suffix='.nii.gz') | ||
ants.image_read(tmpfile) | ||
|
||
|
||
def test_brain_mask_segmentation(self): | ||
metric = compare_images(self.gt_deepMask, self.pred_deepMask) | ||
print("overlap of the brain mask with the label: {}".format(metric)) | ||
# set relative tolerance to 0.05 | ||
# predicted image is expected to have overlap within 0.05 | ||
nptest.assert_allclose(1., metric, rtol=0.05, atol=0) | ||
|
||
|
||
def test_deepFCD_segmentation_mean(self): | ||
metric = compare_images(self.gt_deepFCD_mean, self.pred_deepFCD_mean, metric_type='correlation') | ||
print("correlation of the mean probability map with the the label: {}".format(metric)) | ||
# set relative tolerance to 0.05 | ||
# predicted image is expected to have correlation within 0.05 | ||
nptest.assert_allclose(1., metric, rtol=0.05, atol=0) | ||
|
||
|
||
def test_deepFCD_segmentation_var(self): | ||
metric = compare_images(self.gt_deepFCD_var, self.pred_deepFCD_var, metric_type='correlation') | ||
print("correlation of the mean uncertainty map with the the label: {}".format(metric)) | ||
# set relative tolerance to 0.05 | ||
# predicted image is expected to have correlation within 0.05 | ||
nptest.assert_allclose(1., metric, rtol=0.05, atol=0) | ||
|
||
|
||
if __name__ == '__main__': | ||
unittest.main() |
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,85 @@ | ||
import ants | ||
import numpy as np | ||
|
||
def dilate_labels(label, dilated_label_fname): | ||
""" | ||
Apply morphological operations to an image | ||
ANTsR function: `morphology` | ||
Arguments | ||
--------- | ||
input : ANTsImage | ||
input image | ||
operation : string | ||
operation to apply | ||
"close" Morpholgical closing | ||
"dilate" Morpholgical dilation | ||
"erode" Morpholgical erosion | ||
"open" Morpholgical opening | ||
radius : scalar | ||
radius of structuring element | ||
mtype : string | ||
type of morphology | ||
"binary" Binary operation on a single value | ||
"grayscale" Grayscale operations | ||
value : scalar | ||
value to operation on (type='binary' only) | ||
shape : string | ||
shape of the structuring element ( type='binary' only ) | ||
"ball" spherical structuring element | ||
"box" box shaped structuring element | ||
"cross" cross shaped structuring element | ||
"annulus" annulus shaped structuring element | ||
"polygon" polygon structuring element | ||
radius_is_parametric : boolean | ||
used parametric radius boolean (shape='ball' and shape='annulus' only) | ||
thickness : scalar | ||
thickness (shape='annulus' only) | ||
lines : integer | ||
number of lines in polygon (shape='polygon' only) | ||
include_center : boolean | ||
include center of annulus boolean (shape='annulus' only) | ||
Returns | ||
------- | ||
ANTsImage | ||
Example | ||
------- | ||
>>> import ants | ||
>>> fi = ants.image_read( ants.get_ants_data('r16') , 2 ) | ||
>>> mask = ants.get_mask( fi ) | ||
>>> dilated_ball = ants.morphology( mask, operation='dilate', radius=3, mtype='binary', shape='ball') | ||
>>> eroded_box = ants.morphology( mask, operation='erode', radius=3, mtype='binary', shape='box') | ||
>>> opened_annulus = ants.morphology( mask, operation='open', radius=5, mtype='binary', shape='annulus', thickness=2) | ||
""" | ||
label = ants.image_read(label) | ||
ants.morphology(label, operation='dilate', radius=30, mtype='binary', shape='ball').to_filename(dilated_label_fname) | ||
|
||
|
||
def compare_images(predicted_image, ground_truth_image, metric_type='correlation'): | ||
""" | ||
Measure similarity between two images. | ||
NOTE: Similarity is actually returned as distance (i.e. dissimilarity) | ||
per ITK/ANTs convention. E.g. using Correlation metric, the similarity | ||
of an image with itself returns -1. | ||
""" | ||
# predicted_image = ants.image_read(predicted_image) | ||
# ground_truth_image = ants.image_read(ground_truth_image) | ||
if metric_type == 'correlation': | ||
metric = ants.image_similarity(predicted_image, ground_truth_image, metric_type='ANTSNeighborhoodCorrelation') | ||
metric = np.abs(metric) | ||
else: | ||
metric = ants.label_overlap_measures(predicted_image, ground_truth_image).TotalOrTargetOverlap[1] | ||
|
||
return metric |