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
Tissue classification using MAP-MRF #670
Changes from 10 commits
e4b0086
06e38b4
8f2e852
0de50d9
b5bf3b7
f868551
5139d32
b0a7a4f
3735c9a
f4b5109
8051df0
cb45ebc
32d0ba0
d2efd6d
cf4657d
212c195
a3da077
4faf3a5
f0c087f
e792954
5c20f9a
cb39827
7bc2b67
c93d8c5
b8c1120
878bde1
03758db
97357a0
ada5dbe
283537c
806bedd
e1ce75c
0a8a108
da52ab8
68026c3
efecb38
b54966b
6792fe8
ef895d1
6b10040
ae64760
2318f7d
7301f26
2702ca2
d058418
37e9fa8
ee1c79b
6982df6
66d32c4
1e166af
362c1e5
fe68238
28af494
de5d579
214d9d1
32b34de
c96dbc9
155acc3
9cb9948
e244d99
cacb81e
7b32f44
f38f527
1ceef86
bb71ae3
d24f56a
db0777c
91c8a60
533fdb2
769798d
3cf2f51
a86211c
0b8671e
1f96e95
42d181b
5dac80f
0a4c930
55eae9e
92394eb
00a9601
28ffc52
a95841a
d0cd11e
f1b265f
c5fb1e4
c26eeea
dec9bbc
b6fc1b6
5f2b0ea
981a408
5eeaa0c
2fd9e3a
643859f
838a003
c7ca3b6
980ac77
c145186
aede49b
9526f46
ad52e8d
54a07aa
26576eb
2b052d2
57ef121
578e965
df47d21
caeee84
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
from __future__ import division, print_function, absolute_import | ||
import numpy as np | ||
|
||
|
||
def total_energy(masked_image, masked_segmentation, | ||
mu, var, index, label, beta): | ||
|
||
energytotal = log_likelihood(masked_image, mu, var, index, label) | ||
energytotal += gibbs_energy(masked_segmentation, index, label, beta) | ||
|
||
return energytotal | ||
|
||
|
||
def log_likelihood(img, mu, var, index, label): | ||
|
||
loglike = ((img[index] - mu[label]) ** 2) / (2 * var[label]) | ||
|
||
loglike += np.log(np.sqrt(var[label])) | ||
|
||
return loglike | ||
|
||
|
||
def gibbs_energy(seg, index, label, beta): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Iterating over the neighbors of a voxel can be coded more compactly by defining arrays of offsets: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is great. Yes I saw this somewhere else, but first wanted to try the simpler version of it to test it out. Thanks a lot. |
||
|
||
energy = 0 | ||
|
||
if label == seg[index[0] - 1, index[1], index[2]]: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you need to check if you are at the boundary of the image and do not accumulate the energy in that case, otherwise this will wrap to the other side in Python (not the desired behavior) and may cause a memory access violation in Cython (I think we will definitely need to cythonize) |
||
energy = energy - beta | ||
else: | ||
energy = energy + beta | ||
|
||
if label == seg[index[0] + 1, index[1], index[2]]: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just curious: here, the Ising/Potts potential is -beta if the labels are equal and +beta if the labels are different. I remember that I have seen this "symmetric" version before, but it is not the standard Ising/Potts potential, it should be -beta*Delta(x_{i}-x_{j}). In other words: it should be zero if the labels are equal instead of -beta, as appears in Zhang's paper, first paragraph page 49. Any reason why we prefer the symmetric version here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hi Omar, Thanks for you comment. Can you point me to where exactly in the Zhang paper they mention the "non-symmetric" version? Thanks There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure!, it's 6th line, first paragraph There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry, sixt line, first paragraph, page 49 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, you are right. I completely missed that one. Thanks! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hi Omar, one question regarding the Delta function. Do you know if anyone has implemented it in Dipy? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't know, but it would be something like
|
||
energy = energy - beta | ||
else: | ||
energy = energy + beta | ||
|
||
if label == seg[index[0], index[1] - 1, index[2]]: | ||
energy = energy - beta | ||
else: | ||
energy = energy + beta | ||
|
||
if label == seg[index[0], index[1] + 1, index[2]]: | ||
energy = energy - beta | ||
else: | ||
energy = energy + beta | ||
|
||
if label == seg[index[0], index[1], index[2] - 1]: | ||
energy = energy - beta | ||
else: | ||
energy = energy + beta | ||
|
||
if label == seg[index[0], index[1], index[2] + 1]: | ||
energy = energy - beta | ||
else: | ||
energy = energy + beta | ||
|
||
return energy |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
|
||
|
||
# Use ICM to segment T1 image with HMRF | ||
|
||
import numpy as np | ||
import nibabel as nib | ||
|
||
img = nib.load('/Users/jvillalo/Documents/GSoC_2015/Code/Data/3587_BL_T1_to_MNI_Linear_6p.nii.gz') | ||
dataimg = img.get_data() | ||
|
||
mask_image = nib.load('/Users/jvillalo/Documents/GSoC_2015/Code/Data/3587_mask.nii.gz') | ||
datamask = mask_image.get_data() | ||
|
||
from dipy.segment.mask import applymask | ||
masked_img = applymask(dataimg,datamask) | ||
print('masked_img.shape (%d, %d, %d)' % masked_img.shape) | ||
shape=masked_img.shape[:3] | ||
|
||
# Still must do Otsu for 3 classes. | ||
#from init_param import otsu_param | ||
#mu_tissue1, sig_tissue1, mu_tissue2, sig_tissue2 = otsu_param(masked_img, 4, 4) | ||
|
||
seg = nib.load('/Users/jvillalo/Documents/GSoC_2015/Code/Data/FAST/3587_BL_T1_to_MNI_Linear_6p_seg.nii.gz') | ||
seg_initial = seg.get_data() | ||
|
||
nclass = 3 | ||
nh = 6 #neighborhood | ||
niter = 1 | ||
totalE = np.zeros((shape[0],shape[1],shape[2],nclass)) | ||
|
||
from dipy.segment.rois_stats import seg_stats | ||
seg_stats(masked_img, seg_initial, 3) | ||
|
||
from dipy.core.ndindex import ndindex | ||
|
||
for idx in np.ndindex(shape): | ||
if not masked_img[idx]: | ||
continue | ||
|
||
while True: | ||
|
||
mu, std = seg_stats(masked_img, seg_initial, 3) | ||
|
||
for l in range(0,nclass-3): | ||
totalE = 1/(2*vars[l])*(masked_img-mus[l])^2 + np.log(sigs[l]) - beta*Himg[:,:,:,l] | ||
np.amin(totalE[-1]) | ||
|
||
N = niter+1 | ||
if N == 1: | ||
break | ||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
|
||
import numpy as np | ||
from dipy.segment.mask import multi_median | ||
from dipy.segment.threshold import otsu | ||
|
||
def otsu_param(input_volume, median_radius=4, numpass=4): | ||
|
||
input_filtered = multi_median(input_volume, median_radius, numpass) | ||
thresh = otsu(input_filtered) | ||
tissue1 = input_filtered > thresh | ||
tissue2 = input_filtered < thresh | ||
|
||
mu_tissue1 = np.mean(tissue1) | ||
sig_tissue1 = np.std(tissue1) | ||
mu_tissue2 = np.mean(tissue2) | ||
sig_tissue2 = np.std(tissue2) | ||
|
||
return mu_tissue1, sig_tissue1, mu_tissue2, sig_tissue2 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
|
||
|
||
# Compute the neighborhood. For now only 6. La | ||
|
||
import numpy as np | ||
|
||
def neighbor3D(input_volume,nhood): | ||
|
||
volume_shape = input_volume.shape[:3] | ||
Nhood = np.ones((volume_shape[0],volume_shape[1],volume_shape[2],nhood)) | ||
|
||
|
||
|
||
a = np.arange(shape[0]) | ||
b = np.arange(shape[1]) | ||
c = np.arange(shape[2]) | ||
|
||
A = a[1:-1] | ||
B = b[1:-1] | ||
C = c[1:-1] | ||
|
||
Nhood1 = input_volume[] | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
from __future__ import division, print_function, absolute_import | ||
import numpy as np | ||
|
||
|
||
def seg_stats(input_image, seg_image, nclass): | ||
r""" Mean and standard variation for 3 tissue classes | ||
|
||
1 is CSF | ||
2 is gray matter | ||
3 is white matter | ||
|
||
Parameters | ||
---------- | ||
input_image : ndarray | ||
blah blah | ||
|
||
|
||
|
||
Returns | ||
------- | ||
mu, std : float | ||
Mean and standard deviation for every class | ||
|
||
|
||
""" | ||
mu = np.zeros(3) | ||
std = np.zeros(3) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You have nclass as an argument than you hard code 3 here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Try this:
|
||
|
||
for i in range(1, nclass + 1): | ||
|
||
H = input_image[seg_image == i] | ||
|
||
mu[i - 1] = np.mean(H, -1) | ||
std[i - 1] = np.std(H, -1) | ||
|
||
|
||
return mu, std |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
# Use ICM to segment T1 image with MRF | ||
|
||
import numpy as np | ||
import nibabel as nib | ||
|
||
from dipy.segment.mask import applymask | ||
from dipy.core.ndindex import ndindex | ||
from dipy.segment.rois_stats import seg_stats | ||
from dipy.segment.energy_mrf import total_energy | ||
|
||
# dname = '/Users/jvillalo/Documents/GSoC_2015/Code/Data/' | ||
dname = '/home/eleftherios/Dropbox/DIPY_GSoC_2015/' | ||
|
||
img = nib.load(dname + '3587_BL_T1_to_MNI_Linear_6p.nii.gz') | ||
dataimg = img.get_data() | ||
|
||
mask_image = nib.load(dname + '3587_mask.nii.gz') | ||
datamask = mask_image.get_data() | ||
|
||
masked_img = applymask(dataimg, datamask) | ||
print('masked_img.shape (%d, %d, %d)' % masked_img.shape) | ||
shape = masked_img.shape[:3] | ||
|
||
seg = nib.load(dname + '3587_BL_T1_to_MNI_Linear_6p_seg.nii.gz') | ||
seg_init = seg.get_data() | ||
seg_init_masked = applymask(seg_init, datamask) | ||
|
||
print("computing the statistics of the ROIs (CSF, GM, WM)") | ||
mu, std = seg_stats(masked_img, seg_init_masked, 3) | ||
|
||
print(mu) | ||
print(std) | ||
|
||
nclass = 3 | ||
L = range(1, nclass + 1) | ||
niter = 1 | ||
beta = 0.05 | ||
totalE = np.zeros(nclass) | ||
N = 0 | ||
|
||
segmented = np.zeros(dataimg.shape) | ||
|
||
while True: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. With |
||
|
||
mu, std = seg_stats(masked_img, seg_init, 3) | ||
var = std ** 2 | ||
|
||
for idx in ndindex(shape): | ||
# print(idx) | ||
if not masked_img[idx]: | ||
continue | ||
for l in range(0, nclass): | ||
|
||
totalE[l] = total_energy(masked_img, seg_init_masked, | ||
mu, var, idx, l, beta) | ||
|
||
segmented[idx] = L[np.argmin(totalE)] | ||
|
||
N = N + 1 | ||
if N == niter: | ||
break | ||
|
||
|
||
# print('Show results') | ||
figure() | ||
imshow(seg_init_masked[:, :, 95]) | ||
figure() | ||
imshow(segmented[:, :, 95]) | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -37,3 +37,46 @@ def otsu(image, nbins=256): | |
idx = np.argmax(variance12) | ||
threshold = bin_centers[:-1][idx] | ||
return threshold | ||
|
||
def otsu_3(image, nbins=256): | ||
|
||
""" | ||
Return two threshold values based on Otsu's method. | ||
It is intended to work on a masked T1 brain image. | ||
It is intended to give threshold values for CSF/WM and WM/GM. | ||
|
||
Copied from scikit-image to remove dependency. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please don't forget to copy over the license from scikit-image as well There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is also some stuff you can probably reuse from http://nipy.org/dipy/reference/dipy.segment.html#dipy.segment.mask.median_otsu |
||
|
||
Parameters | ||
---------- | ||
image : array | ||
Input image. | ||
nbins : int | ||
Number of bins used to calculate histogram. This value is ignored for | ||
integer arrays. | ||
|
||
Returns | ||
------- | ||
threshold : float | ||
Threshold values. | ||
""" | ||
|
||
hist, bin_centers = np.histogram(image, nbins) | ||
hist = hist.astype(np.float) | ||
|
||
# class probabilities for all possible thresholds | ||
weight1 = np.cumsum(hist) | ||
weight2 = np.cumsum(hist[::-1])[::-1] | ||
|
||
# class means for all possible thresholds | ||
mean1 = np.cumsum(hist * bin_centers[1:]) / weight1 | ||
mean2 = (np.cumsum((hist * bin_centers[1:])[::-1]) / weight2[::-1])[::-1] | ||
|
||
# Clip ends to align class 1 and class 2 variables: | ||
# The last value of `weight1`/`mean1` should pair with zero values in | ||
# `weight2`/`mean2`, which do not exist. | ||
variance12 = weight1[:-1] * weight2[1:] * (mean1[:-1] - mean2[1:])**2 | ||
|
||
idx = np.argmax(variance12) | ||
threshold = bin_centers[:-1][idx] | ||
return threshold |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is actually the negative log-likelihood, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes it is. My understanding is that this is the one you minimize. The positive one is the one that you would maximize, is that right?
Thanks
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that's right, I just thought that something like
neg_log_likelihood
would be a better name for this function