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

Tissue classification using MAP-MRF #670

Closed
wants to merge 107 commits into from
Closed
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
107 commits
Select commit Hold shift + click to select a range
e4b0086
first commit PVE
villalonreina Jun 8, 2015
06e38b4
Modified the ICM function and added code to the Otsu s
villalonreina Jun 17, 2015
8f2e852
Modified the ICM code
villalonreina Jun 26, 2015
0de50d9
Corrected stats for classes
Garyfallidis Jun 26, 2015
b5bf3b7
Updated test
Garyfallidis Jun 26, 2015
f868551
Merge pull request #1 from Garyfallidis/pve
villalonreina Jun 26, 2015
5139d32
Fixed the energy function
villalonreina Jun 26, 2015
b0a7a4f
Changed likelihood and corrected style
Garyfallidis Jun 26, 2015
3735c9a
RF: changed variable in gibbs energy
Garyfallidis Jun 26, 2015
f4b5109
Reducing beta
Garyfallidis Jun 26, 2015
8051df0
Merge branch 'pve' of https://github.com/villalonreina/dipy into vill…
omarocegueda Jun 27, 2015
cb45ebc
RF: refactor the ICM algorithm into a single class
omarocegueda Jun 27, 2015
32d0ba0
added equations 25, 26 and 27 to tes_icm_map
villalonreina Jul 1, 2015
d2efd6d
Created function for ICM itself to loop through it at every EM iterat…
villalonreina Jul 1, 2015
cf4657d
Corrected negative symbol for P_L_N
Garyfallidis Jul 1, 2015
212c195
Created new file called icm_map.py and modified test_EM.py
villalonreina Jul 1, 2015
a3da077
modified test_EM.py to resolve conflict
villalonreina Jul 1, 2015
4faf3a5
Fixed Ising function error
Garyfallidis Jul 1, 2015
f0c087f
Found problem with l range
Garyfallidis Jul 1, 2015
e792954
Made chnages to most of the functions implemeted in test_em.py
villalonreina Jul 2, 2015
5c20f9a
Fixed the problem of more than one iteration to update the parameters…
villalonreina Jul 3, 2015
cb39827
DOC: Starting new example on classification
Garyfallidis Jul 3, 2015
7bc2b67
DOC: added title
Garyfallidis Jul 3, 2015
c93d8c5
Updated files: example, energy_mrf.py, icm_map.py, rois_stats.py, tes…
villalonreina Jul 8, 2015
b8c1120
Modified test_em.py
villalonreina Jul 8, 2015
878bde1
Modified test_em.py. Added tests for specific functions
villalonreina Jul 9, 2015
03758db
Minor changes
Garyfallidis Jul 9, 2015
97357a0
Added more tests for the ICM as well as for the EM functions. Changed…
villalonreina Jul 16, 2015
ada5dbe
Modified tests in test_em.py
villalonreina Jul 17, 2015
283537c
Merge remote-tracking branch 'jesus/villalonreina-pve' into pve
Garyfallidis Jul 17, 2015
806bedd
BF: added missing numpy import and started adding wrapper python files
Garyfallidis Jul 17, 2015
e1ce75c
Created new file called icm_segmenter.py that contains all the icm se…
villalonreina Jul 18, 2015
0a8a108
Merge branch 'pve' of https://github.com/villalonreina/dipy into pve
Garyfallidis Jul 18, 2015
da52ab8
Updated test_em.py
villalonreina Jul 18, 2015
68026c3
TEST: adding more tests for Cythonized segmenter
Garyfallidis Jul 18, 2015
efecb38
Python function for cythonized neglog likelihood available
Garyfallidis Jul 18, 2015
b54966b
Added Python wrapper for iterate_icm_ising
Garyfallidis Jul 18, 2015
6792fe8
Created new files called segmentation.pyx with all classes and method…
villalonreina Jul 22, 2015
ef895d1
RF: segmentation.pyx is now mrf.pyx
Garyfallidis Jul 22, 2015
6b10040
RF: mainly refactoring mrf.pyx
Garyfallidis Jul 22, 2015
ae64760
RF: more refactoring of python code to cython and vice versa in mrf.pyx
Garyfallidis Jul 22, 2015
2318f7d
TEST: mrf.pyx compiled so let's update the test_mrf.py
Garyfallidis Jul 22, 2015
7301f26
TEST: Added more tests for functions in mrf.py
villalonreina Jul 24, 2015
2702ca2
Updated test_mrf.py. Added tests and debugged many of the methods in …
villalonreina Jul 26, 2015
d058418
Removed applymask from update
Garyfallidis Jul 26, 2015
37e9fa8
modified iteration test in test_mrf.py
villalonreina Jul 26, 2015
ee1c79b
Merge branch 'pve' of https://github.com/villalonreina/dipy into pve
Garyfallidis Jul 26, 2015
6982df6
Merge branch 'pve' of https://github.com/Garyfallidis/dipy into pve
villalonreina Jul 26, 2015
66d32c4
Debugging mode
Garyfallidis Jul 26, 2015
1e166af
Merge branch 'pve' of https://github.com/Garyfallidis/dipy into pve
villalonreina Jul 26, 2015
362c1e5
Made changes to the test file again. Debugged prob_neighborhood metho…
villalonreina Jul 29, 2015
fe68238
Modified mrf.pyx. Debbuged methods prob_image and neg_loglikelihood
villalonreina Jul 29, 2015
28af494
Moved python functions from pyx file to test file for debugging
villalonreina Jul 31, 2015
de5d579
Debugged test_mrf. Made sure to not divide by zeros
villalonreina Aug 1, 2015
214d9d1
Added checks for singularities, sigmasq=0 or log(0)
villalonreina Aug 1, 2015
32b34de
Updated the tests. Made sure they are all passing
villalonreina Aug 1, 2015
c96dbc9
TEST: removed old tests for mrf
Garyfallidis Aug 1, 2015
155acc3
Cythonized prob_image
Garyfallidis Aug 1, 2015
9cb9948
TEST: added tests for non-greyscale T1 images
villalonreina Aug 6, 2015
e244d99
WIP: ready to add mask
villalonreina Aug 10, 2015
cacb81e
RF: changed npy_x functions to standard math.h functions and uncommen…
Garyfallidis Aug 11, 2015
7b32f44
Added test with gaussian noise in tha background of the image
villalonreina Aug 11, 2015
f38f527
Fixed conflicts
villalonreina Aug 11, 2015
1ceef86
trying different things with 0 boundary
Garyfallidis Aug 12, 2015
bb71ae3
Moved things as they were but left the 1 + log there
Garyfallidis Aug 13, 2015
d24f56a
Now creating memory to store new segmentation in ising
Garyfallidis Aug 13, 2015
db0777c
Initiating infinity for min_energy
Garyfallidis Aug 13, 2015
91c8a60
We are zeroing the P_L_N before calling _prob_neighb
Garyfallidis Aug 13, 2015
533fdb2
Added more prints
Garyfallidis Aug 13, 2015
769798d
Minor changes
Garyfallidis Aug 13, 2015
3cf2f51
More tests on Negloglikelihood
villalonreina Aug 14, 2015
a86211c
Added energy plot
Garyfallidis Aug 16, 2015
0b8671e
Added formatter
Garyfallidis Aug 16, 2015
1f96e95
Resolving conflict
Garyfallidis Aug 16, 2015
42d181b
Saving current version
Garyfallidis Aug 17, 2015
5dac80f
Minor update
Garyfallidis Aug 17, 2015
0a4c930
Finished tests
villalonreina Aug 19, 2015
55eae9e
Fixed verbose attribute in ImageSegmenter. Tests are passing
villalonreina Aug 19, 2015
92394eb
Updated test_square_iter()
villalonreina Aug 19, 2015
00a9601
Created new file called tissue.py with the class TissueClassifierHMRF
villalonreina Aug 19, 2015
28ffc52
Debugged test for TissueClassifierHMRF
villalonreina Aug 19, 2015
a95841a
Merge branch 'nipy-dipy-master' into pve
villalonreina Aug 19, 2015
d0cd11e
Added tests for save_history=True
villalonreina Aug 20, 2015
f1b265f
Removed unused files
villalonreina Aug 20, 2015
c5fb1e4
Added tissue.py. MOdifies tissue_classification example file
villalonreina Aug 20, 2015
c26eeea
Fixed test, which had a large number of slices and iterations
villalonreina Aug 21, 2015
dec9bbc
Commented addition of new background class
villalonreina Aug 21, 2015
b6fc1b6
Modified tissue classification example
villalonreina Aug 21, 2015
5f2b0ea
Added link to tissue classification example
villalonreina Aug 21, 2015
981a408
BF: Removed set_printoptions which was affecting other tests too
Garyfallidis Aug 21, 2015
5eeaa0c
Modified the example file with better images and explanations
villalonreina Aug 21, 2015
2fd9e3a
Latest modifications of the example file
villalonreina Aug 21, 2015
643859f
Merge pull request #2 from Garyfallidis/pve
villalonreina Aug 21, 2015
838a003
NF: added data for tissue classifier tutorial
Garyfallidis Aug 21, 2015
c7ca3b6
Merge pull request #3 from Garyfallidis/pve
villalonreina Aug 21, 2015
980ac77
Merge branch 'pve' of https://github.com/Garyfallidis/dipy into pve
villalonreina Aug 21, 2015
c145186
Merge branch 'pve' of https://github.com/villalonreina/dipy into pve
villalonreina Aug 21, 2015
aede49b
Added an image of an anisotropic power map in the fetcher/reader
Garyfallidis Aug 21, 2015
9526f46
Changed downloading message
Garyfallidis Aug 21, 2015
ad52e8d
Modified example file. Fixed figures and grammar
villalonreina Aug 23, 2015
54a07aa
Merge pull request #4 from Garyfallidis/pve
villalonreina Aug 24, 2015
26576eb
Implemented changes suggested since August 21st
villalonreina Nov 3, 2015
2b052d2
Merge branch 'nipy-dipy-master' into pve
villalonreina Nov 8, 2015
57ef121
Added tolerance value as a percentage to stop iterations of the ICM l…
villalonreina Apr 29, 2016
578e965
Merge remote-tracking branch 'nipy-dipy/master' into pve
villalonreina Apr 29, 2016
df47d21
Included convergence criterion (tolerance) as an input to function cl…
villalonreina May 3, 2016
caeee84
Fixed and modified the test file that was erroring. Made small change…
villalonreina Jun 16, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
57 changes: 57 additions & 0 deletions dipy/segment/energy_mrf.py
@@ -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):
Copy link
Contributor

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?

Copy link
Contributor Author

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

Copy link
Contributor

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


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):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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]]:
Copy link
Contributor

Choose a reason for hiding this comment

The 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]]:
Copy link
Contributor

Choose a reason for hiding this comment

The 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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure!, it's 6th line, first paragraph $V_{C}(x) = -\delta(x_{i}-x_{j})$ (the Dirac delta)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, sixt line, first paragraph, page 49

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you are right. I completely missed that one. Thanks!

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know, but it would be something like

return 1 if x == 0 else 0

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
59 changes: 59 additions & 0 deletions dipy/segment/icm_map.py
@@ -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









19 changes: 19 additions & 0 deletions dipy/segment/init_param.py
@@ -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

24 changes: 24 additions & 0 deletions dipy/segment/neighborh.py
@@ -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[]


37 changes: 37 additions & 0 deletions dipy/segment/rois_stats.py
@@ -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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You have nclass as an argument than you hard code 3 here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Try this:

import numpy as np

def seg_stats(input_image, seg_image, ddof=0):
    img = input_image.ravel()
    seg = seg_image.ravel()
    if not isinstance(seg.dtype, np.integer):
        seg = seg.astype(np.int64)

    seg_vol = np.bincount(seg)
    sum_img = np.bincount(seg, img)
    sum_sq = np.bincount(seg, img ** 2)

    mean = sum_img / seg_vol
    var = sum_sq / (seg_vol + ddof) - mean ** 2
    std = np.sqrt(var)
    return mean[1:], std[1:]


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
70 changes: 70 additions & 0 deletions dipy/segment/tests/test_icm_map.py
@@ -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:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With for N in range(niter): you no longer need the break at lines 60-61


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])


43 changes: 43 additions & 0 deletions dipy/segment/threshold.py
Expand Up @@ -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.
Copy link
Contributor

Choose a reason for hiding this comment

The 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

Copy link
Contributor

Choose a reason for hiding this comment

The 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