Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
st2d.py 4.21 KiB
#!/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])