Gaussian Mixture Models of an Image's Histogram - python

I am attempting to do automatic image segmentation of the different regions of a 2D MR image based on pixel intensity values. The first step is implementing a Gaussian Mixture Model on the image's histogram.
I need to plot the resulting gaussian obtained from the score_samples method onto the histogram. I have tried following the code in the answer to (Understanding Gaussian Mixture Models).
However, the resulting gaussian fails to match the histogram at all. How do I get the gaussian to match the histogram?
import numpy as np
import cv2
import matplotlib.pyplot as plt
from sklearn.mixture import GaussianMixture
# Read image
img = cv2.imread("test.jpg",0)
hist = cv2.calcHist([img],[0],None,[256],[0,256])
hist[0] = 0 # Removes background pixels
# Fit GMM
gmm = GaussianMixture(n_components = 3)
gmm = gmm.fit(hist)
# Evaluate GMM
gmm_x = np.linspace(0,255,256)
gmm_y = np.exp(gmm.score_samples(gmm_x.reshape(-1,1)))
# Plot histograms and gaussian curves
fig, ax = plt.subplots()
ax.hist(img.ravel(),255,[1,256])
ax.plot(gmm_x, gmm_y, color="crimson", lw=4, label="GMM")
ax.set_ylabel("Frequency")
ax.set_xlabel("Pixel Intensity")
plt.legend()
plt.show()
I also attempted manually constructing the gaussians with sums.
import numpy as np
import cv2
import matplotlib.pyplot as plt
from sklearn.mixture import GaussianMixture
def gauss_function(x, amp, x0, sigma):
return amp * np.exp(-(x - x0) ** 2. / (2. * sigma ** 2.))
# Read image
img = cv2.imread("test.jpg",0)
hist = cv2.calcHist([img],[0],None,[256],[0,256])
hist[0] = 0 # Removes background pixels
# Fit GMM
gmm = GaussianMixture(n_components = 3)
gmm = gmm.fit(hist)
# Evaluate GMM
gmm_x = np.linspace(0,255,256)
gmm_y = np.exp(gmm.score_samples(gmm_x.reshape(-1,1)))
# Construct function manually as sum of gaussians
gmm_y_sum = np.full_like(gmm_x, fill_value=0, dtype=np.float32)
for m, c, w in zip(gmm.means_.ravel(), gmm.covariances_.ravel(), gmm.weights_.ravel()):
gauss = gauss_function(x=gmm_x, amp=1, x0=m, sigma=np.sqrt(c))
gmm_y_sum += gauss / np.trapz(gauss, gmm_x) * w
# Plot histograms and gaussian curves
fig, ax = plt.subplots()
ax.hist(img.ravel(),255,[1,256])
ax.plot(gmm_x, gmm_y, color="crimson", lw=4, label="GMM")
ax.plot(gmm_x, gmm_y_sum, color="black", lw=4, label="Gauss_sum", linestyle="dashed")
ax.set_ylabel("Frequency")
ax.set_xlabel("Pixel Intensity")
plt.legend()
plt.show()
With ax.hist(img.ravel(),255,[1,256], normed=True)

The issue was with passing the histogram rather than the array of pixel intensities to GaussianMixture.fit gmm = gmm.fit(hist).
I also found that a minimum of n_components = 6 is needed to visually fit this particular histogram.
import numpy as np
import cv2
import matplotlib.pyplot as plt
from sklearn.mixture import GaussianMixture
# Read image
img = cv2.imread("test.jpg",0)
hist = cv2.calcHist([img],[0],None,[256],[0,256])
hist[0] = 0 # Removes background pixels
data = img.ravel()
data = data[data != 0]
data = data[data != 1] #Removes background pixels (intensities 0 and 1)
# Fit GMM
gmm = GaussianMixture(n_components = 6)
gmm = gmm.fit(X=np.expand_dims(data,1))
# Evaluate GMM
gmm_x = np.linspace(0,253,256)
gmm_y = np.exp(gmm.score_samples(gmm_x.reshape(-1,1)))
# Plot histograms and gaussian curves
fig, ax = plt.subplots()
ax.hist(img.ravel(),255,[2,256], normed=True)
ax.plot(gmm_x, gmm_y, color="crimson", lw=4, label="GMM")
ax.set_ylabel("Frequency")
ax.set_xlabel("Pixel Intensity")
plt.legend()
plt.show()

Related

3D plot with multiple curves of data (frequency spectra) and color gradients to highlight the z-axis (magnitudes)

I'm trying to plot a series of frequency spectra in a 3D space using PolyCollection. My goal is to set "facecolors" as a gradient, i.e., the higher the magnitude, the lighter the color.
Please see this image for reference (I am not looking for the fancy design, just the gradients).
I tried to use the cmap argument of the PollyCollection, but I was unsuccessful.
I came this far with the following code adapted from here:
import matplotlib.pyplot as plt
from matplotlib.collections import PolyCollection
from mpl_toolkits.mplot3d import axes3d
import numpy as np
from scipy.ndimage import gaussian_filter1d
def plot_poly(magnitudes):
freq_data = np.arange(magnitudes.shape[0])[:,None]*np.ones(magnitudes.shape[1])[None,:]
mag_data = magnitudes
rad_data = np.linspace(1,magnitudes.shape[1],magnitudes.shape[1])
verts = []
for irad in range(len(rad_data)):
xs = np.concatenate([[freq_data[0,irad]], freq_data[:,irad], [freq_data[-1,irad]]])
ys = np.concatenate([[0],mag_data[:,irad],[0]])
verts.append(list(zip(xs, ys)))
poly = PolyCollection(verts, edgecolor='white', linewidths=0.5, cmap='Greys')
poly.set_alpha(.7)
fig = plt.figure(figsize=(24, 16))
ax = fig.add_subplot(111, projection='3d', proj_type = 'ortho')
ax.add_collection3d(poly, zs=rad_data, zdir='y')
ax.set_xlim3d(freq_data.min(), freq_data.max())
ax.set_xlabel('Frequency')
ax.set_ylim3d(rad_data.min(), rad_data.max())
ax.set_ylabel('Measurement')
ax.set_zlabel('Magnitude')
# Remove gray panes and axis grid
ax.xaxis.pane.fill = False
ax.xaxis.pane.set_edgecolor('white')
ax.yaxis.pane.fill = False
ax.yaxis.pane.set_edgecolor('white')
ax.zaxis.pane.fill = False
ax.zaxis.pane.set_edgecolor('white')
ax.view_init(50,-60)
plt.show()
sample_data = np.random.rand(2205, 4)
sample_data = gaussian_filter1d(sample_data, sigma=10, axis=0) # Just to smoothe the curves
plot_poly(sample_data)
Besides the missing gradients I am happy with the output of the code above.

How to add gaussian noise to an image in the frequency domain using numpy?

I'm new to image processing and I'm wondering how to add a gaussian noise to grayscale image in the frequency domain. I know how to do this in the spatial domain.
import imageio
import numpy as np
import matplotlib.pyplot as plt
mean = 50
var = 50
sigma = var ** 0.5
image = imageio.imread('Pictures/Picture.jpg')
noisy_image = np.copy(image)
for i in range(image.shape[0]):
for j in range(image.shape[1]):
noisy_image[i][j] += np.random.normal(mean, sigma, (1,1))
figure, a = plt.subplots(1,2)
a[0].imshow(image, cmap='gray')
a[0].set_title('original image')
a[1].imshow(noisy_image, cmap='gray')
a[1].set_title('noisy image')
plt.show()
But when I try the same process in the frequency domain it won't work.
import imageio
import numpy as np
import matplotlib.pyplot as plt
mean = 50
var = 50
sigma = var ** 0.5
image = imageio.imread('Pictures/Picture.jpg')
noisy_image = np.copy(image)
# transformation to the frequency domain
noisy_image = np.fft.fft2(noisy_image)
for i in range(image.shape[0]):
for j in range(image.shape[1]):
noisy_image[i][j] += np.complex(np.random.normal(mean, sigma, (1,1)))
# transformation back to the spatial domain
image_noisy = np.real(np.fft.ifft2(image_noisy))
figure, a = plt.subplots(1,2)
a[0].imshow(image, cmap='gray')
a[0].set_title('original image')
a[1].imshow(noisy_image, cmap='gray')
a[1].set_title('noisy image')
plt.show()

Accounting for noise in 2D Gaussian model

I need to fit a 2D gaussian embedded into substantial uniform noise, as shown in the left plot below. I tried using sklearn.mixture.GaussianMixture with two components (code at the bottom), but this obviously fails as shown in the right plot below.
I want to assign probabilities to each element of belonging to the 2D Gaussian and to the uniform background noise. This seems like a simple enough task but I've found no "simple" way to do it.
Any advices? It doesn't need to be GMM, I'm open to other methods/packages.
import numpy as np
import matplotlib.pyplot as plt
from sklearn import mixture
# Generate 2D Gaussian data
N_c = 100
xy_c = np.random.normal((.5, .5), .05, (N_c, 2))
# Generate uniform noise
N_n = 1000
xy_n = np.random.uniform(.0, 1., (N_n, 2))
# Combine into a single data set
data = np.concatenate([xy_c, xy_n])
# fit a Gaussian Mixture Model with two components
model = mixture.GaussianMixture(n_components=2, covariance_type='full')
model.fit(data)
probs = model.predict_proba(data)
labels = model.predict(data)
# Separate the two clusters for plotting
msk0 = labels == 0
c0, p0 = data[msk0], probs[msk0].T[0]
msk1 = labels == 1
c1, p1 = data[msk1], probs[msk1].T[1]
# Plot
plt.subplot(121)
plt.scatter(*xy_n.T, c='b', alpha=.5)
plt.scatter(*xy_c.T, c='r', alpha=.5)
plt.xlim(0., 1.)
plt.ylim(0., 1.)
plt.subplot(122)
plt.scatter(*c0.T, c=p0, alpha=.75)
plt.scatter(*c1.T, c=p1, alpha=.75)
plt.colorbar()
# display predicted scores by the model as a contour plot
X, Y = np.meshgrid(np.linspace(0., 1.), np.linspace(0., 1.))
XX = np.array([X.ravel(), Y.ravel()]).T
Z = -model.score_samples(XX)
Z = Z.reshape(X.shape)
plt.contour(X, Y, Z)
plt.show()
I think kernel density can help you to localize the gaussian and exclude point outside of it (e.g in area with lesser densities)
Here is an example code :
import numpy as np
import matplotlib.pyplot as plt
from sklearn import mixture
from sklearn.neighbors import KernelDensity
# Generate 2D Gaussian data
N_c = 100
xy_c = np.random.normal((.2, .2), .05, (N_c, 2))
# Generate uniform noise
N_n = 1000
xy_n = np.random.uniform(.0, 1., (N_n, 2))
# Combine into a single data set
data = np.concatenate([xy_c, xy_n])
print(data.shape)
model = KernelDensity(kernel='gaussian',bandwidth=0.05)
model.fit(data)
probs = model.score_samples(data)
# Plot
plt.subplot(131)
plt.scatter(*xy_n.T, c='b', alpha=.5)
plt.scatter(*xy_c.T, c='r', alpha=.5)
plt.xlim(0., 1.)
plt.ylim(0., 1.)
# plot kernel score
plt.subplot(132)
plt.scatter(*data.T, c=probs, alpha=.5)
# display predicted scores by the model as a contour plot
X, Y = np.meshgrid(np.linspace(0., 1.), np.linspace(0., 1.))
XX = np.array([X.ravel(), Y.ravel()]).T
Z = -model.score_samples(XX)
Z = Z.reshape(X.shape)
plt.contour(X, Y, Z)
plt.xlim(0,1)
plt.ylim(0,1)
# plot kernel score with threshold
plt.subplot(133)
plt.scatter(*data.T, c=probs>0.5, alpha=.5) # here you can adjust the threshold
plt.colorbar()
plt.xlim(0,1)
plt.ylim(0,1)
And this is the output figure :
I changed the center of the gaussian to ensure my code was working. The right panel display the kernel score with a threshold, which can be use in your case to filter out the noisy data outside of the gaussian, but you can't filter the noise inside the gaussian.

heatmap for gauge/indicator

I have a collection of (sparse) data that has temperature measurements. With a heatmap, areas that have more observations show a higher value because the heatmap accumulates the values.
Is there a way to get more of an average as opposed to a sum? But also with the feel of gaussian filtering. If no data is in a region, a 0 value would be preferred (which would be transparent).
If you would like to gaussian filter, see ndimage.gaussian_filter
Here's an example:
import matplotlib.pyplot as plt
import numpy as np
import scipy.ndimage
fig = plt.figure()
# Random example data with some values set to 0
im = np.random.random((10, 10))
im[im < 0.3] = 0
# Smooth image
smoothed_im = scipy.ndimage.filters.gaussian_filter(im, sigma=1)
im[im == 0] = None
plt.imshow(im, interpolation = "nearest")
plt.title("Original image")
plt.colorbar()
plt.figure()
plt.imshow(smoothed_im, interpolation = "nearest")
plt.title("Smoothed image")
plt.colorbar()
# Blank elements that were originally 0
smoothed_im[np.isnan(im)] = None
plt.figure()
plt.imshow(smoothed_im, interpolation = "nearest")
plt.title("Smoothed image with original zeros blanked")
plt.colorbar()
This produces:

Why performing an histogram equalization by scikit image to a binary image i got a black image after mahotas conversion?

I used histogram equalization and adaptation for erase illumination from the grayscale images, but after the histogram equalization (i used scikit image python library) was good, during image conversion in mahotas something goes wrong. I got a picture total black. How can i fix it?
Source image:
Histogram equalization and adaptation;
Result after mahotas conversion.
conversion code from scikit to mahotas:
binimg = np.array(img_adapteq, dtype=np.bool)
Source code:
import scipy
import numpy as np
import pymorph as pm
import mahotas as mh
from skimage import morphology
from skimage import io
from matplotlib import pyplot as plt
from skimage import data, img_as_float
from skimage import exposure
def plot_img_and_hist(img, axes, bins=256):
"""Plot an image along with its histogram and cumulative histogram.
"""
img = img_as_float(img)
ax_img, ax_hist = axes
ax_cdf = ax_hist.twinx()
# Display image
ax_img.imshow(img, cmap=plt.cm.gray)
ax_img.set_axis_off()
# Display histogram
ax_hist.hist(img.ravel(), bins=bins, histtype='step', color='black')
ax_hist.ticklabel_format(axis='y', style='scientific', scilimits=(0, 0))
ax_hist.set_xlabel('Pixel intensity')
ax_hist.set_xlim(0, 1)
ax_hist.set_yticks([])
# Display cumulative distribution
img_cdf, bins = exposure.cumulative_distribution(img, bins)
ax_cdf.plot(bins, img_cdf, 'r')
ax_cdf.set_yticks([])
return ax_img, ax_hist, ax_cdf
mhgray = mh.imread(path,0)
binimg = mhgray[:,:,0]
print(type(binimg[0][0]))
thresh = mh.otsu(binimg)
gray =( binimg< thresh)
shape = list(gray.shape)
w = 0
if (shape[0] > shape[1]):
shape = shape[0]
else:
shape = shape[1]
if (shape < 100):
w = int((shape/100 )*1.5)
elif(shape > 100 and shape <420):
w = int((shape/100 )*2.5)
else:
w = int((shape/100)*4)
disk7 = pm.sedisk(w)
img = binimg
# Contrast stretching
p2 = np.percentile(img, 2)
p98 = np.percentile(img, 98)
img_rescale = exposure.rescale_intensity(img, in_range=(p2, p98))
# Equalization
img_eq = exposure.equalize_hist(img)
# Adaptive Equalization
img_adapteq = exposure.equalize_adapthist(img, clip_limit=0.03)
# Display results
f, axes = plt.subplots(2, 4, figsize=(8, 4))
ax_img, ax_hist, ax_cdf = plot_img_and_hist(img, axes[:, 0])
ax_img.set_title('Low contrast image')
y_min, y_max = ax_hist.get_ylim()
ax_hist.set_ylabel('Number of pixels')
ax_hist.set_yticks(np.linspace(0, y_max, 5))
ax_img, ax_hist, ax_cdf = plot_img_and_hist(img_rescale, axes[:, 1])
ax_img.set_title('Contrast stretching')
ax_img, ax_hist, ax_cdf = plot_img_and_hist(img_eq, axes[:, 2])
ax_img.set_title('Histogram equalization')
ax_img, ax_hist, ax_cdf = plot_img_and_hist(img_adapteq, axes[:, 3])
ax_img.set_title('Adaptive equalization')
ax_cdf.set_ylabel('Fraction of total intensity')
ax_cdf.set_yticks(np.linspace(0, 1, 5))
# prevent overlap of y-axis labels
plt.subplots_adjust(wspace=0.4)
plt.show()
plt.gray()
plt.subplot(121)
plt.title("after histo")
plt.imshow(img_adapteq)
plt.show()
binimg = np.array(img_adapteq, dtype=np.bool)#uint16
plt.gray()
plt.subplot(121)
plt.title("after otsu")
plt.imshow(binimg)
plt.show()
imgbnbin = mh.morph.dilate(binimg, disk7)
#2
plt.gray()
plt.subplot(121)
plt.title("after dilate before close")
plt.imshow(imgbnbin)
plt.show()
imgbnbin = mh.morph.close(imgbnbin, disk7)
#2
plt.gray()
plt.subplot(121)
plt.title("before skeletonize")
plt.imshow(imgbnbin)
plt.show()
imgbnbin = mh.morph.close(imgbnbin, disk7)
out = morphology.skeletonize(imgbnbin>0)
The scikit-image algorithm probably returns a floating point image with values between 0 and 1. If you cast that to bool, you'll get all ones. You probably want
binimg = img_adapteq > 0.5
In general, also take note of the rescale_intensity function, which will take an image with values between 0 and 1 and return an image with values between 0 and 255.
from skimage import exposure
image = rescale_intensity(image, out_range=(0, 255))

Categories

Resources