Python Image segementation and extraction - python

I am trying to figure out how to extract multiple objects from a greyscale image with mineral grains. I need to segment the different grains and then extract each of them to save as a separate image file.
While doing research, I have found that everyone uses skimage. I'm worried that some grains will not be extracted (mineralgrains).
Has anyone had to work on a similar problem?

I'm not entirely sure what the mineral grains are in this image, but segmenting and labeling connected components is pretty straight forward using skimage algorithms.
The simplest approach that I know of is accomplished in two major steps:
Generate a binary image. The most straight forward methods for this are using thresholding based methods such as those elaborated on here: https://scikit-image.org/docs/dev/auto_examples/segmentation/plot_thresholding.html
Separating connected objects. This is often accomplished through use of a watershed algorithm as elaborated on here: https://scikit-image.org/docs/stable/auto_examples/segmentation/plot_watershed.html
Assuming that the mineral grains are the bigger objects in the example image, a first pass approach may look something like the following
import numpy as np
from scipy import ndimage as ndi
from skimage import io
from skimage.filters import gaussian, threshold_li
from skimage.morphology import remove_small_holes, remove_small_objects
from skimage.segmentation import watershed
from skimage.feature import peak_local_max
from skimage.measure import label
# read in the image
img = io.imread('fovAK.png', as_gray=True)
gauss_sigma = 1 # standard deviation for Gaussian kernel for removing a bit of noise from image
obj_min_size = 5000 # minimum # of pixels for labeled objects
min_hole_size = 5000 # The maximum area, in pixels, of a contiguous hole that will be filled
watershed_footprin = 100 # size of local region within which to search for peaks for watershed
#########################
#make a binary image using threshold
#########################
img_blurred = gaussian(img, gauss_sigma) # remove a bit of noise from image
img_thresh = threshold_li(img_blurred) # use histogram based method to determine intensity threshold
img_binary = img_blurred > img_thresh # make a binary image
# make a border around the image so that holes at edge are not filled
ydims, xdims = img.shape
img_binary[0, :] = 0
img_binary[ydims-1, :] = 0
img_binary[:, 0] = 0
img_binary[:, xdims-1] = 0
img_rsh = remove_small_holes(img_binary, min_hole_size) # removes small holdes in binary image
img_rso = remove_small_objects(img_rsh, obj_min_size) # removes small objects in binary image
#############################
#use watershed algorithm to seperate connected objects
####################################
distance = ndi.distance_transform_edt(img_rso) # distance transform of image
coords = peak_local_max(distance, labels=img_rso, footprint=np.ones((ws_footprint, ws_footprint))) # local pead distances
mask = np.zeros(distance.shape, dtype=bool) # mask for watershed
mask[tuple(coords.T)] = True
markers, _ = ndi.label(mask)
labels = watershed(-distance, markers, mask=img_rso)
This is absolutely a first pass on your example image and is by no means perfect. You may need to use a different thresholding algorithm or tweak other parameters but hopefully this gets you moving in the right direction.
From there you can just iterate over the labeled objects and save individual images.
There are definitely more sophisticated approaches to these kinds of problems ranging from edge detectors and bandpass filters all the out to using ML based methods but hopefully this gets you moving in the right direction.

Related

Merge image-segments depending on length of the watershed-line in-between using Python, Numpy and Scikit-Image/OpenCV

I am working on a watershedding-based segmentation algorithm to segment fluorescence images such as this one:
As result I obtain a Numpy array with labels for each segment. These are separated by a watershed lines, if the corresponding regions in the fluorescence image have a sufficiently large intensity-drop-off between them. For very large intensity-drop-offs they are completely separated through simple thresholding. The result for the image above is this:
My algorithm performs well for the vast majority of cases. However, it sometimes it has a slight tendency to oversegment. Such as in this case from the image above:
Since these cases will be difficult to improve by working further on the intensity-based segmentation itself (and I run the risk of breaking other things), I want to instead selectively merge adjacent segments based on the length of the watershed-line between them and the averaged maximum width of the two segments above and below.
I know what I have to do on a pixel-for-pixel basis:
Find pixels that have two different label-values in their direct neighborhood. Store these pixels separately for each segment-pair (with corresponding segment-labels).
Calculate the number of these pixels for each pair of adjacent segments to obtain the length of the watershed-line.
Calculate the maximum width (horizontally for simplicity) of the adjacent segments.
Merge the adjacent segments, if the watershed-line is longer than a given threshold-fraction (user-defined) of the averaged width of the two segments. I could do this by converting the labels to a binary mask, filling the watershed line using the stored pixels where applicable, and relabelling the binary mask.
Since in Python iterating over individual pixels is generally slow, I am unsure how to write performant code for this. Therefore I am looking for suggestions on how to implement this with Numpy and Skimage (OpenCV is also an option).
You didn't provide how you got your initial segments. Despite this, I think improving the watershed lines could solve your problem and this can be done in the watershed hierarchy framework, with the Higra package.
I specify an initial ordering of the watershed by the image complement and recompute its watershed lines with another attribute (volume).
The intensity drop and area that you describe are the volume attribute, and you can control the segmentation by its threshold in the hierarchy.
Here it is a working example:
import cv2
import numpy as np
import higra as hg
from skimage.morphology import remove_small_objects, label
import matplotlib.pyplot as plt
def main():
img_path = "fig.png"
img = cv2.imread(img_path)
img = img[:,:,0].copy()
img = img.max() - img
size = img.shape[:2]
graph = hg.get_4_adjacency_graph(size)
edge_weights = hg.weight_graph(graph, img, hg.WeightFunction.mean)
tree, altitudes = hg.quasi_flat_zone_hierarchy(graph, edge_weights)
attr = hg.attribute_volume(tree, altitudes)
saliency = hg.saliency(tree, attr)
# Take a look at this :)
# grid = hg.graph_4_adjacency_2_khalimsky(graph, saliency)
# plt.imshow(grid)
# plt.show()
attr_thold = np.mean(saliency) / 4 # arbitrary
area_thold = 500 # arbitrary
segments = hg.labelisation_horizontal_cut_from_threshold(tree, attr, attr_thold)
segments = label(remove_small_objects(segments, area_thold))
plt.imshow(segments)
plt.show()
if __name__ == "__main__":
main()
Here it is the result.

Edge detection for image stored in matrix

I represent images in the form of 2-D arrays. I have this picture:
How can I get the pixels that are directly on the boundaries of the gray region and colorize them?
I want to get the coordinates of the matrix elements in green and red separately. I have only white, black and gray regions on the matrix.
The following should hopefully be okay for your needs (or at least help). The idea is to split into the various regions using logical checks based on threshold values. The edge between these regions can then be detected using numpy roll to shift pixels in x and y and comparing to see if we are at an edge,
import matplotlib.pyplot as plt
import numpy as np
import scipy as sp
from skimage.morphology import closing
thresh1 = 127
thresh2 = 254
#Load image
im = sp.misc.imread('jBD9j.png')
#Get threashold mask for different regions
gryim = np.mean(im[:,:,0:2],2)
region1 = (thresh1<gryim)
region2 = (thresh2<gryim)
nregion1 = ~ region1
nregion2 = ~ region2
#Plot figure and two regions
fig, axs = plt.subplots(2,2)
axs[0,0].imshow(im)
axs[0,1].imshow(region1)
axs[1,0].imshow(region2)
#Clean up any holes, etc (not needed for simple figures here)
#region1 = sp.ndimage.morphology.binary_closing(region1)
#region1 = sp.ndimage.morphology.binary_fill_holes(region1)
#region1.astype('bool')
#region2 = sp.ndimage.morphology.binary_closing(region2)
#region2 = sp.ndimage.morphology.binary_fill_holes(region2)
#region2.astype('bool')
#Get location of edge by comparing array to it's
#inverse shifted by a few pixels
shift = -2
edgex1 = (region1 ^ np.roll(nregion1,shift=shift,axis=0))
edgey1 = (region1 ^ np.roll(nregion1,shift=shift,axis=1))
edgex2 = (region2 ^ np.roll(nregion2,shift=shift,axis=0))
edgey2 = (region2 ^ np.roll(nregion2,shift=shift,axis=1))
#Plot location of edge over image
axs[1,1].imshow(im)
axs[1,1].contour(edgex1,2,colors='r',lw=2.)
axs[1,1].contour(edgey1,2,colors='r',lw=2.)
axs[1,1].contour(edgex2,2,colors='g',lw=2.)
axs[1,1].contour(edgey2,2,colors='g',lw=2.)
plt.show()
Which gives the . For simplicity I've use roll with the inverse of each region. You could roll each successive region onto the next to detect edges
Thank you to #Kabyle for offering a reward, this is a problem that I spent a while looking for a solution to. I tried scipy skeletonize, feature.canny, topology module and openCV with limited success... This way was the most robust for my case (droplet interface tracking). Hope it helps!
There is a very simple solution to this: by definition any pixel which has both white and gray neighbors is on your "red" edge, and gray and black neighbors is on the "green" edge. The lightest/darkest neighbors are returned by the maximum/minimum filters in skimage.filters.rank, and a binary combination of masks of pixels that have a lightest/darkest neighbor which is white/gray or gray/black respectively produce the edges.
Result:
A worked solution:
import numpy
import skimage.filters.rank
import skimage.morphology
import skimage.io
# convert image to a uint8 image which only has 0, 128 and 255 values
# the source png image provided has other levels in it so it needs to be thresholded - adjust the thresholding method for your data
img_raw = skimage.io.imread('jBD9j.png', as_grey=True)
img = numpy.zeros_like(img, dtype=numpy.uint8)
img[:,:] = 128
img[ img_raw < 0.25 ] = 0
img[ img_raw > 0.75 ] = 255
# define "next to" - this may be a square, diamond, etc
selem = skimage.morphology.disk(1)
# create masks for the two kinds of edges
black_gray_edges = (skimage.filters.rank.minimum(img, selem) == 0) & (skimage.filters.rank.maximum(img, selem) == 128)
gray_white_edges = (skimage.filters.rank.minimum(img, selem) == 128) & (skimage.filters.rank.maximum(img, selem) == 255)
# create a color image
img_result = numpy.dstack( [img,img,img] )
# assign colors to edge masks
img_result[ black_gray_edges, : ] = numpy.asarray( [ 0, 255, 0 ] )
img_result[ gray_white_edges, : ] = numpy.asarray( [ 255, 0, 0 ] )
imshow(img_result)
P.S. Pixels which have black and white neighbors, or all three colors neighbors, are in an undefined category. The code above doesn't color those. You need to figure out how you want the output to be colored in those cases; but it is easy to extend the approach above to produce another mask or two for that.
P.S. The edges are two pixels wide. There is no getting around that without more information: the edges are between two areas, and you haven't defined which one of the two areas you want them to overlap in each case, so the only symmetrical solution is to overlap both areas by one pixel.
P.S. This counts the pixel itself as its own neighbor. An isolated white or black pixel on gray, or vice versa, will be considered as an edge (as well as all the pixels around it).
While plonser's answer may be rather straight forward to implement, I see it failing when it comes to sharp and thin edges. Nevertheless, I suggest you use part of his approach as preconditioning.
In a second step you want to use the Marching Squares Algorithm. According to the documentation of scikit-image, it is
a special case of the marching cubes algorithm (Lorensen, William and
Harvey E. Cline. Marching Cubes: A High Resolution 3D Surface
Construction Algorithm. Computer Graphics (SIGGRAPH 87 Proceedings)
21(4) July 1987, p. 163-170
There even exists a Python implementation as part of the scikit-image package. I have been using this algorithm (my own Fortran implementation, though) successfully for edge detection of eye diagrams in communications engineering.
Ad 1: Preconditioning
Create a copy of your image and make it two color only, e.g. black/white. The coordinates remain the same, but you make sure that the algorithm can properly make a yes/no-decision independent from the values that you use in your matrix representation of the image.
Ad 2: Edge Detection
Wikipedia as well as various blogs provide you with a pretty elaborate description of the algorithm in various languages, so I will not go into it's details. However, let me give you some practical advice:
Your image has open boundaries at the bottom. Instead of modifying the algorithm, you can artifically add another row of pixels (black or grey to bound the white/grey areas).
The choice of the starting point is critical. If there are not too many images to be processed, I suggest you select it manually. Otherwise you will need to define rules. Since the Marching Squares Algorithm can start anywhere inside a bounded area, you could choose any pixel of a given color/value to detect the corresponding edge (it will initially start walking in one direction to find an edge).
The algorithm returns the exact 2D positions, e.g. (x/y)-tuples. You can either
iterate through the list and colorize the corresponding pixels by assigning a different value or
create a mask to select parts of your matrix and assign the value that corresponds to a different color, e.g. green or red.
Finally: Some Post-Processing
I suggested to add an artificial boundary to the image. This has two advantages:
1. The Marching Squares Algorithm works out of the box.
2. There is no need to distinguish between image boundary and the interface between two areas within the image. Just remove the artificial boundary once you are done setting the colorful edges -- this will remove the colored lines at the boundary of the image.
Basically by follow pyStarter's suggestion of using the marching square algorithm from scikit-image, the desired could contours can be extracted with the following code:
import matplotlib.pyplot as plt
import numpy as np
import scipy as sp
from skimage import measure
import scipy.ndimage as ndimage
from skimage.color import rgb2gray
from pprint import pprint
#Load image
im = rgb2gray(sp.misc.imread('jBD9j.png'))
n, bins_edges = np.histogram(im.flatten(),bins = 100)
# Skip the black area, and assume two distinct regions, white and grey
max_counts = np.sort(n[bins_edges[0:-1] > 0])[-2:]
thresholds = np.select(
[max_counts[i] == n for i in range(max_counts.shape[0])],
[bins_edges[0:-1]] * max_counts.shape[0]
)
# filter our the non zero values
thresholds = thresholds[thresholds > 0]
fig, axs = plt.subplots()
# Display image
axs.imshow(im, interpolation='nearest', cmap=plt.cm.gray)
colors = ['r','g']
for i, threshold in enumerate(thresholds):
contours = measure.find_contours(im, threshold)
# Display all contours found for this threshold
for n, contour in enumerate(contours):
axs.plot(contour[:,1], contour[:,0],colors[i], lw = 4)
axs.axis('image')
axs.set_xticks([])
axs.set_yticks([])
plt.show()
!
However, from your image there is no clear defined gray region, so I took the two largest counts of intensities in the image and thresholded on these. A bit disturbing is the red region in the middle of the white region, however I think this could be tweaked with the number of bins in the histogram procedure. You could also set these manually as Ed Smith did.
Maybe there is a more elegant way to do that ...
but in case your array is a numpy array with dimensions (N,N) (gray scale) you can do
import numpy as np
# assuming black -> 0 and white -> 1 and grey -> 0.5
black_reg = np.where(a < 0.1, a, 10)
white_reg = np.where(a > 0.9, a, 10)
xx_black,yy_black = np.gradient(black_reg)
xx_white,yy_white = np.gradient(white_reg)
# getting the coordinates
coord_green = np.argwhere(xx_black**2 + yy_black**2>0.2)
coord_red = np.argwhere(xx_white**2 + yy_white**2>0.2)
The number 0.2 is just a threshold and needs to be adjusted.
I think you are probably looking for edge detection method for gray scale images. There are many ways to do that. Maybe this can help http://en.m.wikipedia.org/wiki/Edge_detection. For differentiating edges between white and gray and edges between black and gray, try use local average intensity.

Python Scikit-image processing of Gel electrophoresis data

I never used computer vision stuff before and thought I can use python for analysis of Gel Electrophoresis. Here is a video explaining what is happening if you are not familiar with the process.
So I took a pic from wikipedia of a gel then use a grayscale filter, then a bilateral filter to get rid of smudges and artifacts, and then I used a Otsu filter to separate out the prominent bands.
import numpy as np
import matplotlib.pyplot as plt
from skimage import data, io
from skimage.filter import threshold_otsu, denoise_bilateral
from skimage.morphology import closing, square
from skimage.measure import regionprops
from skimage.color import label2rgb, rgb2gray
image = io.imread('http://upload.wikimedia.org/wikipedia/commons/6/60/Gel_electrophoresis_2.jpg')
#grayscaling
gray_image = rgb2gray(image)
# bilateral filtering
bilat=denoise_bilateral(gray_image, sigma_range=0.05, sigma_spatial=20)
# apply threshold Otsu
thresh = threshold_otsu(bilat)
bw = closing(bilat > thresh, square(1))
#print process
def show_images(images,titles=None):
"""Display a list of images"""
n_ims = len(images)
if titles is None: titles = ['(%d)' % i for i in range(1,n_ims + 1)]
fig = plt.figure()
n = 1
for image,title in zip(images,titles):
a = fig.add_subplot(1,n_ims,n)
if image.ndim == 2:
plt.gray()
plt.imshow(image)
a.set_title(title)
n += 1
fig.set_size_inches(np.array(fig.get_size_inches()) * n_ims)
plt.show()
#print data
show_images(images=[image, bilat, bw], titles=['Normal', 'Bilateral filter', 'Otsu Threshold'])
Here is what the results currently look like
I have 4 problems I got stuck on:
Using the otsu threshold causes some data loss from light color bands is there better way get the band data?
Is there a way to return the results from each row to a numpy/pandas array where the bands are displayed on a matrix? (ie 0 for no bands, 1 for light band, 2 for medium band, 3 for heavy band) This will allow detecting bands that are matching with the DNA Ladder(reference row).
What method can be used to calculate the distance from the wells to the bands.
If the picture is not taken straight would I need something called Image registration? If so where do I find it in scikit-image?
Last thing I am using python 3 and the last stable version of scikit-image if it matters.
Perhaps get in touch with the authors of https://github.com/hugadams/pyparty, which is built on top of scikit-image.
You may want to first equalize the image (see the "exposure" submodule)
You'll first have to do some kind of peak detection (see the "feature" submodule)
I'm not quite sure what you are asking here
Rather image warping (see the "transform" submodule)

Counting particles using image processing in python

Is there any good algorithm for detecting particles on a changing background intensity?
For example, if I have the following image:
Is there a way to count the small white particles, even with the clearly different background that appears towards the lower left?
To be a little more clear, I would like to label the image and count the particles with an algorithm that finds these particles to be significant:
I have tried many things with the PIL, cv , scipy , numpy , etc. modules.
I got some hints from this very similar SO question, and it appears at first glance that you could take a simple threshold like so:
im = mahotas.imread('particles.jpg')
T = mahotas.thresholding.otsu(im)
labeled, nr_objects = ndimage.label(im>T)
print nr_objects
pylab.imshow(labeled)
but because of the changing background you get this:
I have also tried other ideas, such as a technique I found for measuring paws, which I implemented in this way:
import numpy as np
import scipy
import pylab
import pymorph
import mahotas
from scipy import ndimage
import cv
def detect_peaks(image):
"""
Takes an image and detect the peaks usingthe local maximum filter.
Returns a boolean mask of the peaks (i.e. 1 when
the pixel's value is the neighborhood maximum, 0 otherwise)
"""
# define an 8-connected neighborhood
neighborhood = ndimage.morphology.generate_binary_structure(2,2)
#apply the local maximum filter; all pixel of maximal value
#in their neighborhood are set to 1
local_max = ndimage.filters.maximum_filter(image, footprint=neighborhood)==image
#local_max is a mask that contains the peaks we are
#looking for, but also the background.
#In order to isolate the peaks we must remove the background from the mask.
#we create the mask of the background
background = (image==0)
#a little technicality: we must erode the background in order to
#successfully subtract it form local_max, otherwise a line will
#appear along the background border (artifact of the local maximum filter)
eroded_background = ndimage.morphology.binary_erosion(background, structure=neighborhood, border_value=1)
#we obtain the final mask, containing only peaks,
#by removing the background from the local_max mask
detected_peaks = local_max - eroded_background
return detected_peaks
im = mahotas.imread('particles.jpg')
imf = ndimage.gaussian_filter(im, 3)
#rmax = pymorph.regmax(imf)
detected_peaks = detect_peaks(imf)
pylab.imshow(pymorph.overlay(im, detected_peaks))
pylab.show()
but this gives no luck either, showing this result:
Using the regional max function, I get images which almost appear to be giving correct particle identification, but there are either too many, or too few particles in the wrong spots depending on my gaussian filtering (images have gaussian filter of 2,3, & 4):
Also, it would need to work on images similar to this as well:
This is the same type of image above, just at a much higher density of particles.
EDIT: Solved solution: I was able to get a decent working solution to this problem using the following code:
import cv2
import pylab
from scipy import ndimage
im = cv2.imread('particles.jpg')
pylab.figure(0)
pylab.imshow(im)
gray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray, (5,5), 0)
maxValue = 255
adaptiveMethod = cv2.ADAPTIVE_THRESH_GAUSSIAN_C#cv2.ADAPTIVE_THRESH_MEAN_C #cv2.ADAPTIVE_THRESH_GAUSSIAN_C
thresholdType = cv2.THRESH_BINARY#cv2.THRESH_BINARY #cv2.THRESH_BINARY_INV
blockSize = 5 #odd number like 3,5,7,9,11
C = -3 # constant to be subtracted
im_thresholded = cv2.adaptiveThreshold(gray, maxValue, adaptiveMethod, thresholdType, blockSize, C)
labelarray, particle_count = ndimage.measurements.label(im_thresholded)
print particle_count
pylab.figure(1)
pylab.imshow(im_thresholded)
pylab.show()
This will show the images like this:
(which is the given image)
and
(which is the counted particles)
and calculate the particle count as 60.
I had solved the "variable brightness in background" by using a tuned difference threshold with a technique called Adaptive Contrast. It works by performing a linear combination (a difference, in the case) of a grayscale image with a blurred version of itself, then applying a threshold to it.
Convolve the image with a suitable statistical operator.
Subtract the original from the convolved image, correcting intensity scale/gamma if necessary.
Threshold the difference image with a constant.
(original paper)
I did this very successfully with scipy.ndimage, in the floating-point domain (way better results than integer image processing), like this:
original_grayscale = numpy.asarray(some_PIL_image.convert('L'), dtype=float)
blurred_grayscale = scipy.ndimage.filters.gaussian_filter(original_grayscale, blur_parameter)
difference_image = original_grayscale - (multiplier * blurred_grayscale);
image_to_be_labeled = ((difference_image > threshold) * 255).astype('uint8') # not sure if it is necessary
labelarray, particle_count = scipy.ndimage.measurements.label(image_to_be_labeled)
Hope this helps!!
I cannot really give a definite answer, but here are a few pointers:
The function mahotas.morph.regmax might be better than the maximum filter as it removes pseudo-maxima. Perhaps combine this with a global threshold, with a local threshold (such as the mean over a window) or both.
If you have several images and the same uneven background, then maybe you can compute an average background and normalize against that, or use empty images as your estimate of background. This would be the case if you have a microscope, and like every microscope I've seen, the illumination is uneven.
Something like:
average = average_of_many(images)
# smooth it
average = mahotas.gaussian_filter(average,24)
Now you preprocess your images, like:
preproc = image/average
or something like that.

python separate round particles by offsetting contours / shrinking polygones

I'm new to python and stuck..
I want to make a python script that allows me to separate adjacent particles on an image like this:
into separate regions like this:
I was suggested to use the watershed method, which as far as I understand it would give me a something like this:
EDIT Actually found out that this is distance transform and not watershed
Where I then could use a threshold to separate them.. Followed this openCV watershed guide but it only worked to cut out the particles. Was not able to "transform" the code to do what I want.
I then took another approach. Tried to use the openCV contours which gave me good contours of the particles. I have then been looking intensively for an easy way to perform polygon offset in order to shrink the edge like this:
Using the center from the offset contours (polygon) should give me the number of particles.. But I just haven been able to find a simple way to do edge offset / polygon shrinking with python.
Here is a script using numpy, scipy and the scikit-image (aka skimage). It makes use of local maxima extraction and watershading plus labeling (ie connected components extraction).
import numpy as np
import scipy.misc
import scipy.ndimage
import skimage.feature
import skimage.morphology
# parameters
THRESHOLD = 128
# read image
im = scipy.misc.imread("JPh65.png")
# convert to gray image
im = im.mean(axis=-1)
# find peaks
peak = skimage.feature.peak_local_max(im, threshold_rel=0.9, min_distance=10)
# make an image with peaks at 1
peak_im = np.zeros_like(im)
for p in peak:
peak_im[p[0], p[1]] = 1
# label peaks
peak_label, _ = scipy.ndimage.label(peak_im)
# propagate peak labels with watershed
labels = skimage.morphology.watershed(255 - im, peak_label)
# limit watershed labels to area where the image is intense enough
result = labels * (im > THRESHOLD)

Categories

Resources