Search code examples
pythonnumpyheatmapgaussian

How to efficiently compute the heat map of two Gaussian distribution in Python?


I am trying to produce a heat map where the pixel values are governed by two independent 2D Gaussian distributions. Let them be Kernel1 (muX1, muY1, sigmaX1, sigmaY1) and Kernel2 (muX2, muY2, sigmaX2, sigmaY2) respectively. To be more specific, the length of each kernel is three times its standard deviation. The first Kernel has sigmaX1 = sigmaY1 and the second Kernel has sigmaX2 < sigmaY2. Covariance matrix of both kernels are diagonal (X and Y are independent). Kernel1 is usually completely inside Kernel2.

I tried the following two approaches and the results are both unsatisfactory. Can someone give me some advice?

Approach1:

Iterate over all pixel value pairs (i, j) on the map and compute the value of I(i,j) given by I(i,j) = P(i, j | Kernel1, Kernel2) = 1 - (1 - P(i, j | Kernel1)) * (1 - P(i, j | Kernel2)). Then I got the following result, which is good in terms of smoothness. But it takes 10 seconds to run on my computer, which is too slow.

Codes:

def genDensityBox(self, height, width, muY1, muX1, muY2, muX2, sigmaK1, sigmaY2, sigmaX2):
    densityBox = np.zeros((height, width))
    for y in range(height):
        for x in range(width):
            densityBox[y, x] += 1. - (1. - multivariateNormal(y, x, muY1, muX1, sigmaK1, sigmaK1)) * (1. - multivariateNormal(y, x, muY2, muX2, sigmaY2, sigmaX2))
    return densityBox

def multivariateNormal(y, x, muY, muX, sigmaY, sigmaX):
    return norm.pdf(y, loc=muY, scale=sigmaY) * norm.pdf(x, loc=muX, scale=sigmaX)

First approach

Approach2:

Generate two images corresponding to two kernels separately and then blend them together using certain alpha value. Each image is generated by taking the outer product of two one-dimensional Gaussian filter. Then I got the following result, which is very crude. But the advantage of this approach is that it is very fast due to the use of outer product between two vectors. Second approach

Since the first one is slow and the second on is crude, I am trying to find a new approach that achieves good smoothness and low time-complexity at the same time. Can someone give me some help?

Thanks!

For the second approach, the 2D Gaussian map can be easily generated as mentioned here:

def gkern(self, sigmaY, sigmaX, yKernelLen, xKernelLen, nsigma=3):
    """Returns a 2D Gaussian kernel array."""
    yInterval = (2*nsigma+1.)/(yKernelLen)
    yRow = np.linspace(-nsigma-yInterval/2.,nsigma+yInterval/2.,yKernelLen + 1)
    kernelY = np.diff(st.norm.cdf(yRow, 0, sigmaY))
    xInterval = (2*nsigma+1.)/(xKernelLen)
    xRow = np.linspace(-nsigma-xInterval/2.,nsigma+xInterval/2.,xKernelLen + 1)
    kernelX = np.diff(st.norm.cdf(xRow, 0, sigmaX))    
    kernelRaw = np.sqrt(np.outer(kernelY, kernelX))
    kernel = kernelRaw / (kernelRaw.sum())
    return kernel

Solution

  • Your approach is fine other than that you shouldn't loop over norm.pdf but just push all values at which you want the kernel(s) evaluated, and then reshape the output to the desired shape of the image.

    import numpy as np
    import matplotlib.pyplot as plt
    from scipy.stats import multivariate_normal
    
    # create 2 kernels
    m1 = (-1,-1)
    s1 = np.eye(2)
    k1 = multivariate_normal(mean=m1, cov=s1)
    
    m2 = (1,1)
    s2 = np.eye(2)
    k2 = multivariate_normal(mean=m2, cov=s2)
    
    # create a grid of (x,y) coordinates at which to evaluate the kernels
    xlim = (-3, 3)
    ylim = (-3, 3)
    xres = 100
    yres = 100
    
    x = np.linspace(xlim[0], xlim[1], xres)
    y = np.linspace(ylim[0], ylim[1], yres)
    xx, yy = np.meshgrid(x,y)
    
    # evaluate kernels at grid points
    xxyy = np.c_[xx.ravel(), yy.ravel()]
    zz = k1.pdf(xxyy) + k2.pdf(xxyy)
    
    # reshape and plot image
    img = zz.reshape((xres,yres))
    plt.imshow(img); plt.show()
    

    enter image description here

    This approach shouldn't take too long:

    In [26]: %timeit zz = k1.pdf(xxyy) + k2.pdf(xxyy)
    1000 loops, best of 3: 1.16 ms per loop