structen.py 7.85 KB
# -*- coding: utf-8 -*-
"""
Created on Sun Mar 12 21:54:40 2017

@author: david
"""

import numpy
import scipy.ndimage
import progressbar
import glob

def st2(I, s=1, dtype=numpy.float32):   
    
    #calculates the 2D structure tensor for an image using a gaussian window with standard deviation s
    
    #calculate the gradient
    dI = numpy.gradient(I)
    
    #calculate the dimensions of the tensor field
    field_dims = [dI[0].shape[0], dI[0].shape[1], 3]
    
    #allocate space for the tensor field
    Tg = numpy.zeros(field_dims, dtype=dtype)
    
    #calculate the gradient components of the tensor
    ti = 0
    for i in range(2):
        for j in range(i + 1):
            Tg[:, :, ti] = dI[j] * dI[i]
            ti = ti + 1
    
    #blur the tensor field
    T = numpy.zeros(field_dims, dtype=dtype)
    
    for i in range(3):
        T[:, :, i] = scipy.ndimage.filters.gaussian_filter(Tg[:, :, i], [s, s])

    
    return T

def st3(I, s=1):
    #calculate the structure tensor field for the 3D input image I given the window size s in 3D
    #check the format for the window size
    if type(s) is not list:
        s = [s] * 3
    elif len(s) == 1:
        s = s * 3
    elif len(s) == 2:
        s.insert(1, s[0])
        
    print("\nCalculating gradient...")
    dI = numpy.gradient(I)
    #calculate the dimensions of the tensor field
    field_dims = [dI[0].shape[0], dI[0].shape[1], dI[0].shape[2], 6]
    
    #allocate space for the tensor field
    Tg = numpy.zeros(field_dims, dtype=numpy.float32)
    
    #calculate the gradient components of the tensor
    ti = 0
    print("Calculating tensor components...")
    bar = progressbar.ProgressBar()
    bar.max_value = 6
    for i in range(3):
        for j in range(i + 1):
            Tg[:, :, :, ti] = dI[j] * dI[i]
            ti = ti + 1
            bar.update(ti)
    
    #blur the tensor field
    T = numpy.zeros(field_dims, dtype=numpy.float32)
    
    print("\nConvolving tensor field...")
    bar = progressbar.ProgressBar()
    bar.max_value = 6
    for i in range(6):
        T[:, :, :, i] = scipy.ndimage.filters.gaussian_filter(Tg[:, :, :, i], s)
        bar.update(i+1)
        
    return T

def st(I, s=1):
    if I.ndim == 3:
        return st3(I, s)
    elif I.ndim == 2:
        return st2(I, s)
    else:
        print("Image must be 2D or 3D")
    return
        
   
    
def sym2mat(T):
    #Calculate the full symmetric matrix from a list of lower triangular elements.
    #The lower triangular components in the input field T are assumed to be the
    #   final dimension of the input matrix.
    
    #       | 1  2  4  7  |
    #       | 0  3  5  8  |
    #       | 0  0  6  9  |
    #       | 0  0  0  10 |
   
    in_s = T.shape
    
    #get the number of tensor elements
    n = in_s[T.ndim - 1]
    
    #calculate the dimension of the symmetric matrix
    d = int(0.5 * (numpy.sqrt(8. * n + 1.) - 1.))
    
    #calculate the dimensions of the output field
    out_s = list(in_s)[:-1] + [d] + [d]

    #allocate space for the output field
    R = numpy.zeros(out_s)
    
    ni = 0
    for i in range(d):
        for j in range(i + 1):
            R[..., i, j] = T[..., ni]
            if i != j:
                R[..., j, i] = T[..., ni]
            ni = ni + 1
    
    return R   

def st2vec(S, vector='largest'):
    #Create a color image from a 2D or 3D structure tensor slice
    
    if(S.ndim != 3):
        print("ERROR: a 2D slice is expected")
        return
      
    #convert the field to a full rank-2 tensor
    T = sym2mat(S);
    del(S)
    
    #calculate the eigenvectors and eigenvalues
    l, v = numpy.linalg.eig(T)
    
    #get the dimension of the tensor field
    d = T.shape[2]
    
    #allocate space for the vector field
    V = numpy.zeros([T.shape[0], T.shape[1], 3])
    
    idx = l.argsort()
    
    for di in range(d):
        if vector == 'smallest':
            b = idx[:, :, 0] == di
        elif vector == 'largest':
            b = idx[:, :, d-1] == di
        else:
            b = idx[:, :, 1] == di
        V[b, 0:d] = v[b, :, di]
    
    return V

def loadstack(filemask):
    #Load an image stack as a 3D grayscale array
    
    #get a list of all files matching the given mask
    files = [file for file in glob.glob(filemask)]
    
    #calculate the size of the output stack
    I = scipy.misc.imread(files[0])
    X = I.shape[0]
    Y = I.shape[1]
    Z = len(files)
    
    #allocate space for the image stack
    M = numpy.zeros([X, Y, Z]).astype('float32')
    
    #create a progress bar
    bar = progressbar.ProgressBar()
    bar.max_value = Z
    
    #for each file
    for z in range(Z):
        #load the file and save it to the image stack
        M[:, :, z] = scipy.misc.imread(files[z], flatten="True").astype('float32')
        bar.update(z+1)
    return M

def anisotropy(S):

    Sf = sym2mat(S)
    
    #calculate the eigenvectors and eigenvalues
    l, v = numpy.linalg.eig(Sf)
    
    #store the sorted eigenvalues
    ls = numpy.sort(l)
    l0 = ls[:, :, 0]
    l1 = ls[:, :, 1]
    l2 = ls[:, :, 2]
    
    #calculate the linear anisotropy
    Cl = (l2 - l1)/(l2 + l1 + l0)
    
    #calculate the planar anisotropy
    Cp = 2 * (l1 - l0) / (l2 + l1 + l0)
    
    #calculate the spherical anisotropy
    Cs = 3 * l0 / (l2 + l1 + l0)
    
    #calculate the fractional anisotropy
    l_hat = (l0 + l1 + l2)/3
    fa_num = (l2 - l_hat) ** 2 + (l1 - l_hat) ** 2 + (l0 - l_hat) ** 2;
    fa_den = l0 ** 2 + l1 ** 2 + l2 ** 2
    FA = numpy.sqrt(3./2.) * numpy.sqrt(fa_num) / numpy.sqrt(fa_den)
    
    return FA, Cl, Cp, Cs

def st2amira(filename, T):
    #generates a tensor field that can be imported into Amira
    
    #   0    dx dx   ----> 0
    #   1    dx dy   ----> 1
    #   2    dy dy   ----> 3
    #   3    dx dz   ----> 2
    #   4    dy dz   ----> 4
    #   5    dz dz   ----> 5
    
    #swap the 2nd and 3rd tensor components
    A = numpy.copy(T)
    A[..., 3] = T[..., 2]
    A[..., 2] = T[..., 3]
    
    #roll the tensor axis so that it is the leading component
    #A = numpy.rollaxis(A, A.ndim - 1)
    A.tofile(filename)
    print("\n", A.shape)

def resample3(T, s=2):
    #resample a tensor field by an integer factor s
    #This function first convolves the field with a box filter and then
    #   re-samples to create a smaller field
    
    #check the format for the window size
    if type(s) is not list:
        s = [s] * 3
    elif len(s) == 1:
        s = s * 3
    elif len(s) == 2:
        s.insert(1, s[0])
    s = numpy.array(s)
    
    bar = progressbar.ProgressBar()
    bar.max_value = T.shape[3]
    
    #blur with a uniform box filter of size r
    for t in range(T.shape[3]):
        T[..., t] = scipy.ndimage.filters.uniform_filter(T[..., t], 2 * s)
        bar.update(t+1)
        
    #resample at a rate of r
    R = T[::s[0], ::s[1], ::s[2], :]
    return R

def color3(prefix, T, vector='largest', aniso=True):
    #Saves a stack of color images corresponding to the eigenvector and optionally scaled by anisotropy
    
    bar = progressbar.ProgressBar()
    bar.max_value = T.shape[2]
    
    #for each z-axis slice
    for z in range(T.shape[2]):
        S = T[:, :, z, :]                           #get the slice
        V = st2vec(S, vector='smallest')   #calculate the vector
        C = numpy.absolute(V)                       #calculate the absolute value
        
        if aniso == True:                              #if the image is scaled by anisotropy
            FA, Cl, Cp, Cs = anisotropy(S)          #calculate the anisotropy of the slice
            if vector == 'largest':
                A = Cl
            elif vector == 'smallest':
                A = Cp
        else:                                       #otherwise just scale by 1
            A = numpy.ones(T.shape[0], T.shape[1])
        image = C * numpy.expand_dims(A, 3)
        
        filename = prefix + str(z).zfill(4) + ".bmp"
        scipy.misc.imsave(filename, image)
        bar.update(z + 1)