Skip to content
Snippets Groups Projects
st2d.py 4.21 KiB
Newer Older
  • Learn to ignore specific revisions
  • vand's avatar
    vand committed
    #!/usr/bin/env python3
    # -*- coding: utf-8 -*-
    """
    Created on Thu Aug 29 11:30:17 2019
    
    @author: vand@dtu.dk
    """
    
    import numpy as np
    import scipy.ndimage
    
    # STRUCTURE TENSOR 2D
    
    def structure_tensor(image, sigma, rho):
        """ Structure tensor for 2D image data
        Arguments:
            image: a 2D array of size N = rows*columns
            sigma: a noise scale, structures smaller than sigma will be 
                removed by smoothing
            rho: an integration scale giving the size over the neighborhood in 
                which the orientation is to be analysed
        Returns:
            an array with shape (3,N) containing elements of structure tensor 
                s_xx, s_yy, s_xy ordered acording to image.ravel()
        Author: vand@dtu.dk, 2019
        """
        
        # computing derivatives (scipy implementation truncates filter at 4 sigma)
        image = image.astype(np.float);
    
        Ix = scipy.ndimage.gaussian_filter(image, sigma, order=[1,0], mode='nearest')
        Iy = scipy.ndimage.gaussian_filter(image, sigma, order=[0,1], mode='nearest')
        
        # integrating elements of structure tensor (scipy uses sequence of 1D)
        Jxx = scipy.ndimage.gaussian_filter(Ix**2, rho, mode='nearest')
        Jyy = scipy.ndimage.gaussian_filter(Iy**2, rho, mode='nearest')
        Jxy = scipy.ndimage.gaussian_filter(Ix*Iy, rho, mode='nearest')
        S = np.vstack((Jxx.ravel(), Jyy.ravel(), Jxy.ravel()));
        return S
    
    def eig_special(S):
        """ Eigensolution for symmetric real 2-by-2 matrices
        Arguments:
            S: an array with shape (3,N) containing structure tensor
        Returns:
            val: an array with shape (2,N) containing sorted eigenvalues
            vec: an array with shape (2,N) containing eigenvector corresponding
                to the smallest eigenvalue (the other is orthogonal to the first)
        Author: vand@dtu.dk, 2019
        """
    
        val = 0.5*(S[0]+S[1]+np.outer(np.array([-1,1]), np.sqrt((S[0]-S[1])**2+4*S[2]**2)))
        vec = np.vstack((-S[2],S[0]-val[0])) # y will be positive
        aligned = S[2]==0 # dealing with diagonal matrices
        vec[:,aligned] = 1-np.argsort(S[0:2,aligned], axis=0)
        vec = vec/np.sqrt(vec[0]**2+vec[1]**2) # normalizing
        return val, vec
    
    def solve_flow(S):
        """ Solving 1D optic flow, returns LLS optimal x for flow along y axis
            ( x is a solution to S[0]*x=S[2] )
        Arguments:
            S: an array with shape (3,N) containing 2D structure tensor
        Returns:
            x: an array with shape (1,N) containing x components of the flow
        Author: vand@dtu.dk, 2019
        """
        aligned = S[0]==0 # 0 or inf solutions
        x = np.zeros((1,S.shape[1]))
        x[0,~aligned] = - S[2,~aligned]/S[0,~aligned]
        return x # returning shape (1,N) array for consistancy with 3D case
    
    #% HELPING FUNCTIONS FOR VISUALIZATION
    
    def plot_orientations(ax, dim, vec, s = 5):
        """ Helping function for adding orientation-quiver to the plot.
        Arguments: plot axes, image shape, orientation, arrow spacing.
        """
        vx = vec[0].reshape(dim)
        vy = vec[1].reshape(dim)
        xmesh, ymesh = np.meshgrid(np.arange(dim[0]), np.arange(dim[1]), indexing='ij')
        ax.quiver(ymesh[s//2::s,s//2::s],xmesh[s//2::s,s//2::s],vy[s//2::s,s//2::s],vx[s//2::s,s//2::s],color='r',angles='xy')
        ax.quiver(ymesh[s//2::s,s//2::s],xmesh[s//2::s,s//2::s],-vy[s//2::s,s//2::s],-vx[s//2::s,s//2::s],color='r',angles='xy')
    
    def polar_histogram(ax, distribution, cmap = 'hsv'):
        """ Helping function for producing polar histogram.
        Arguments: plot axes, oriantation distribution, colormap.
        """
        N = distribution.size
        bin_centers_full = (np.arange(2*N)+0.5)*np.pi/N # full circle (360 deg)
        distribution_full = np.r_[distribution,distribution]/max(distribution) # full normalized distribution
        x = np.r_[distribution_full*np.cos(bin_centers_full),0]
        y = np.r_[distribution_full*np.sin(bin_centers_full),0]
        triangles = np.array([(i, np.mod(i-1,2*N), 2*N) for i in range(2*N)]) # triangles[0] is symmetric over 0 degree
        triangle_centers_full = (np.arange(2*N))*np.pi/N # a triangle covers area BETWEEN two bin_centers
        triangle_colors = np.mod(triangle_centers_full, np.pi)/np.pi # from 0 to 1-(1/2N)
        ax.tripcolor(y, x, triangles, facecolors=triangle_colors, cmap=cmap, vmin = 0.0, vmax = 1.0)
        ax.set_aspect('equal')
        ax.set_xlim([-1,1])
        ax.set_ylim([1,-1])