Applying Multi Otsu Threshold for my image - computer-vision

I have this image shown below
And, here I am trying to define the threshold to distinguish bimodal class by using the Otsu technique based on intensity and then visualise those in the histogram. So far I have written following codes:
import matplotlib.pyplot as plt
import numpy as np
from skimage import data, io, img_as_ubyte
from skimage.filters import threshold_multiotsu
# Read an image
image = io.imread("Fig_1.png")
# Apply multi-Otsu threshold
thresholds = threshold_multiotsu(image,classes=5)
# Digitize (segment) original image into multiple classes.
#np.digitize assign values 0, 1, 2, 3, ... to pixels in each class.
regions = np.digitize(image, bins=thresholds)
output = img_as_ubyte(regions) #Convert 64 bit integer values to uint8
fig, ax = plt.subplots(nrows=1, ncols=3, figsize=(10, 3.5))
# Plotting the original image.
ax[0].imshow(image, cmap='gray')
ax[0].set_title('Original')
ax[0].axis('off')
# Plotting the histogram and the two thresholds obtained from
# multi-Otsu.
ax[1].hist(image.ravel(), bins=255)
ax[1].set_title('Histogram')
for thresh in thresholds:
ax[1].axvline(thresh, color='r')
# Plotting the Multi Otsu result.
ax[2].imshow(regions, cmap='gray')
ax[2].set_title('Multi-Otsu result')
ax[2].axis('off')
plt.subplots_adjust()
plt.show()
This gives me the following result. Here As you can see Multi-Otsu result is totally black and does not show the two class of object present in the figure.
I choose classes=5 but this is bimodal hence putting classes=3 also giving me the same result.
Any advice on how to correct this? Thanks in advance.

Related

segmentation of overlapping cells

The following python script should split overlapping cells apart which does work quite good. The problem is now that it also splits some of the cells apart which don't overlap with other cells. To make things clear to you i'll add my input image and the output image.
The input:input image
The output:
output image
Output image where I marked two "bad" segmented cells:Output image with marked errors
Thresholded image: Thresholded image
Does someone have an idea how to avoid this problem or is the whole approach not good enough to process these kind of images?
I am using the following piece of code to segment the cells:
from skimage.feature import peak_local_max
from skimage.morphology import watershed
from scipy import ndimage
import numpy as np
import cv2
# load the image and perform pyramid mean shift filtering
# to aid the thresholding step
image = cv2.imread('C:/Users/Root/Desktop/image13.jpg')
shifted = cv2.pyrMeanShiftFiltering(image, 41, 51)
# convert the mean shift image to grayscale, then apply
# Otsu's thresholding
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255,
cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
im = gray.copy()
D = ndimage.distance_transform_edt(thresh)
localMax = peak_local_max(D, indices=False, min_distance=3,
labels=thresh)
# perform a connected component analysis on the local peaks,
# using 8-connectivity, then apply the Watershed algorithm
markers = ndimage.label(localMax, structure=np.ones((3, 3)))[0]
labels = watershed(-D, markers, mask=thresh)
print("[INFO] {} unique segments found".format(len(np.unique(labels)) - 1))
conts=[]
for label in np.unique(labels):
# if the label is zero, we are examining the 'background'
# so simply ignore it
if label == 0:
continue
# otherwise, allocate memory for the label region and draw
# it on the mask
mask = np.zeros(gray.shape, dtype="uint8")
mask[labels == label] = 255
# detect contours in the mask and grab the largest one
cnts = cv2.findContours(mask.copy(), cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)[-2]
c = max(cnts, key=cv2.contourArea)
rect = cv2.minAreaRect(c)
box = cv2.boxPoints(rect)
box = np.int0(box)
if cv2.contourArea(c) > 150:
#cv2.drawContours(image,c,-1,(0,255,0))
cv2.drawContours(image,[box],-1,(0,255,0))
cv2.imshow("output", image)
cv2.waitKey()

Show all colors on colorbar with scatter plot

In the following I use scatter and an own ListedColormap to plot some coloured data points. In addition the corresponding colorbar is also plotted.
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap, ListedColormap, BoundaryNorm
from numpy import arange
fig, ax = plt.subplots()
my_cm = ListedColormap(['#a71b1b','#94258f','#ea99e6','#ec9510','#ece43b','#a3f8ff','#2586df','#035e0d'])
bounds=range(8)
norm = BoundaryNorm(bounds, my_cm.N)
data = [1,2,1,3,0,5,3,4]
ret = ax.scatter(range(my_cm.N), [1]*my_cm.N, c=data, edgecolors='face', cmap=my_cm, s=50)
cbar = fig.colorbar(ret, ax=ax, boundaries=arange(-0.5,8,1), ticks=bounds, norm=norm)
cbar.ax.tick_params(axis='both', which='both',length=0)
If my data is not covering each value of the boundary interval, the colorbar does not show all colours (like in the added figure). If data would be set to range(8), I get a dot of each colour and the colorbar also shows all colours.
How can I force the colorbar to show all defined colours even if data does not contain all boundary values?
You need to manually set vminand vmax in your call to ax.scatter:
ret = ax.scatter(range(my_cm.N), [1]*my_cm.N, c=data, edgecolors='face', cmap=my_cm, s=50, vmin=0, vmax=7)
resulting in
If my data is not covering each value of the boundary interval, the colorbar does not show all colours (like in the added figure).
If either vminor vmax are `None the color limits are set via the method
autoscale_None, and the minimum and maximum of your data are therefore used.
So using your code it is actually not necessary for showing all colors in the colorbar that every value of the boundary interval is covered, only the minimum and maximum need to be included.
Using e.g. data = [0,0,0,0,0,0,0,7] results in the following:
When looking for something else, I found another solution to that problem: colorbar-for-matplotlib-plot-surface-command.
In that case, I do not need to set vmin and vmax and it is also working in cases if the arrays/lists of points to plot are empty. Instead a ScalarMappable is defined and provided to colorbar instead of the scatterinstance.
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap, ListedColormap, BoundaryNorm
import matplotlib.cm as cm
from numpy import arange
fig, ax = plt.subplots()
my_cm = ListedColormap(['#a71b1b','#94258f','#ea99e6','#ec9510','#ece43b','#a3f8ff','#2586df','#035e0d'])
bounds=range(8)
norm = BoundaryNorm(bounds, my_cm.N)
mappable = cm.ScalarMappable(cmap=my_cm)
mappable.set_array(bounds)
data = [] # also x and y can be []
ax.scatter(x=range(my_cm.N), y=[1]*my_cm.N, c=data, edgecolors='face', cmap=my_cm, s=50)
cbar = fig.colorbar(mappable, ax=ax, boundaries=arange(-0.5,8,1), ticks=bounds, norm=norm)
cbar.ax.tick_params(axis='both', which='both',length=0)

Set location of xticks in a matplotlib scatter plot

I am trying to create a scatter plot of measurements where the x labels are WIFI channels. By default matplotlib is spacing the labels in proportion to their numerical value. However, I would like them to be spaced uniformly over the scatter plot. Is that possible?
This is basically what my plot code currently looks like:
- where chanPoints is a list of frequencies and measurements is a list of measurements.
plt.scatter(chanPoints,measurements)
plt.xlabel('Frequency (MHz)')
plt.ylabel('EVM (dB)')
plt.xticks(Tchan,rotation = 90)
plt.title('EVM for 5G Channels by Site')
plt.show()
Numpy
You may use numpy to create an array which maps the unique items within chanPoints to numbers 0,1,2.... You can then give each of those numbers the corresponding label.
import matplotlib.pyplot as plt
import numpy as np
chanPoints = [4980, 4920,4920,5500,4980,5500,4980, 5500, 4920]
measurements = [5,6,4,3,5,8,4,6,3]
unique, index = np.unique(chanPoints, return_inverse=True)
plt.scatter(index, measurements)
plt.xlabel('Frequency (MHz)')
plt.ylabel('EVM (dB)')
plt.xticks(range(len(unique)), unique)
plt.title('EVM for 5G Channels by Site')
plt.show()
Seaborn
If you're happy to use seaborn, this can save a lot of manual work. Seaborn is specialized for plotting categorical data. The chanPoints would be interpreted as categories on the x axis, and have the same spacing between them, if you were e.g. using a swarmplot. If several points would then overlap, they are plotted next to each other, which may be an advantage as it allows to see the number of measurement for that channel.
import matplotlib.pyplot as plt
import seaborn.apionly as sns
chanPoints = [4980, 4920,4920,5500,4980,5500,4980, 5500, 4920]
measurements = [5,6,4,3,5,8,4,6,3]
sns.swarmplot(chanPoints, measurements)
plt.xlabel('Frequency (MHz)')
plt.ylabel('EVM (dB)')
plt.title('EVM for 5G Channels by Site')
plt.show()
Replace chanPoints with an index.
index = numpy.searchsorted(Tchan, chanPoints)
plt.scatter(index, measurements)
Then build your xticks with the corresponding lables.
ticks = range(len(Tchan))
plt.xticks(ticks, labels=Tchan, rotation = 90)

Stacking scatter_matrix and matshow

I was using the iris data from sci-kit-learn to obtain following data frame:
df = pd.DataFrame(data= np.c_[iris['data'], iris['target']],
columns= iris['feature_names'] + ['target'])
Plotting the scatter_matrix and using matshow to plot the correlation matrix give me the graphs scatter_matrix plot and
matshow(df.corr()), respectively.
My question is the following. Is there a way to stack these graphs? In other words, plot the scatter_matrix over the matshow(df.corr()) ?
Thanks in advance.
I suppose what you really want is to colorize the background of the respective axes in the color that would appear in a matshow plot of the correlation matrix.
To this end we can find out the color by supplying the normalized (to 0..1) correlation matrix to a matplotlib colormap and change the axes background color using ax.set_facecolor.
import seaborn.apionly as sns
import pandas as pd
import matplotlib.pyplot as plt
# taking the iris from seaborn (should be same as scikit)
df = sns.load_dataset("iris")
axes =pd.scatter_matrix(df)
corr = df.corr().values
corr_norm = (corr-corr.min())/(corr.max()-corr.min())
for i, ax in enumerate(axes.flatten()):
c = plt.cm.viridis(corr_norm.flatten()[i])
ax.set_facecolor(c)
plt.show()

Python OpenCv2, counting contours of colored object

I want to be able to count the number of pixels in a detected object. I'm using the cv2.threshold function. Here is some sudo code.
import cv2
import numpy as np
import time
while True:
cam= cv2.VideoCapture(0)
while(cam.isOpened())
ret, image = cam.read()
image = cv2.GaussianBlur(image, (5,5), 0)
Image1 = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
lower= np.array([30,40,40], dtype='uint8')
upper= np.array([95,240,240], dtype='uint8')
Thresh= cv2.inRange(Image1, lower, upper)
From here on out, I have no idea how to count the pixels of my objects. How do you find the contours of a binary image? I suppose it could be possible to cv2.bitwise_and a full black image over the Thresh/ mask, but that seems like it could be slow and also I don't know how to create a fully black and white image like that.
So TD:LR, how do you count the number of pixels in an object from a binary image?
Note: I'm actually just after the largest object and only need the number of pixels, not the image.
Edit: not trying to count the total number of pixels detected, I've already done that. Want the number of pixels detected from the object with the largest number.
This is how I did it
import cv2
import numpy as np
import time
from scipy.ndimage import (labeled_comprehension, label, measurements, generate_binary_structure) # new import
while True:
cam= cv2.VideoCapture(0)
while(cam.isOpened())
ret, image = cam.read() # record image
image = cv2.GaussianBlur(image, (5,5), 0) # blur to remove noise
Image1 = cv2.cvtColor(image, cv2.COLOR_BGR2HSV) # convert to better color scheme
lower= np.array([30,40,40], dtype='uint8') # low green
upper= np.array([95,240,240], dtype='uint8') # high green
Thresh= cv2.inRange(Image1, lower, upper) # returns array with 255 as pixel if in threshold
struct = generate_binary_structure(2,2) # seems necessary for some reason
Label, features = label(Thresh, struct) # label is object, features is number of objects
Arange = np.arange(1, features+1) # seems necessary for some reason
Biggest = sorted(labeled_comprehension(Thresh, Label, Arange, np.sum, float, -1))[features-1]//255 # counts and organises the objects based on size. [features-1] means last object, ie: biggest. //255 because that's each pixel work (from thresh)