2

I am trying to segment some microscopy bright-field images showing some E. coli bacteria. The picture I am working with resembles this one (even if this one is obtained with phase contrast):

microscopy bright-field image

my problem is that after running my segmentation function (OtsuMask below) I cannot distinguish dividing bacteria (you can try my code below on the sample image). This means that I get one single labeled region for a couple of bacteria which are joined by their end, instead of two different labeled images. The boundary between two dividing bacteria is too narrow to be highlighted by the morphological operations I perform on the thresholded image, but I guess there must be a way to achieve my goal.

Any ideas/suggestions?

import scipy as sp
import numpy as np
from scipy import optimize
import mahotas as mht
from scipy import ndimage
import pylab as plt


def OtsuMask(img,dilation_size=2,erosion_size=1,remove_size=500):

    img_thres=np.asarray(img)
    s=np.shape(img)    
    p0=np.array([0,0,0])

    p0[0]=(img[0,0]-img[0,-1])/512.    
    p0[1]=(img[1,0]-img[1,-1])/512.
    p0[2]=img.mean()

    [x,y]=np.meshgrid(np.arange(s[1]),np.arange(s[0]))

    p=fitplane(img,p0)    
    img=img-myplane(p,x,y)    


    m=img.min()
    img=img-m
    img=abs(img)
    img=img.astype(uint16)

    """perform thresholding with Otsu"""
    T = mht.thresholding.otsu(img,2)
    print T
    img_thres=img
    img_thres[img<T*0.9]=0
    img_thres[img>T*0.9]=1


    img_thres=-img_thres+1  

    """morphological operations"""
    diskD=createDisk(dilation_size)
    diskE=createDisk(erosion_size)

    img_thres=ndimage.morphology.binary_dilation(img_thres,diskD)   

    labeled_im,N=mht.label(img_thres)
    label_sizes=mht.labeled.labeled_size(labeled_im)
    labeled_im=mht.labeled.remove_regions(labeled_im,np.where(label_sizes<remove_size))    


    figure();
    imshow(labeled_im)

    return labeled_im

def myplane(p,x,y):

    return p[0]*x+p[1]*y+p[2] 

def res(p,data,x,y):

    a=(data-myplane(p,x,y));

    return array(np.sum(np.abs(a**2)))

def fitplane(data,p0):

    s=shape(data);

    [x,y]=meshgrid(arange(s[1]),arange(s[0]));
    print shape(x), shape(y)

    p=optimize.fmin(res,p0,args=(data,x,y));
    print p
    return p


def createDisk( size ):
    x, y = np.meshgrid( np.arange( -size, size ), np.arange( -size, size ) )
    diskMask = ( ( x + .5 )**2 + ( y + .5 )**2 < size**2)
    return diskMask

THE FIRST PART OF THE CODE IN OtsuMask CONSIST OF A PLANE FITTING AND SUBTRACTION.

Adi Shavit
  • 16,743
  • 5
  • 67
  • 137
JacoSolari
  • 1,226
  • 14
  • 28

3 Answers3

1

A similar approach to the one described in this related stackoverflow answer can be used here.

It goes basically like this:

  • threshold your image, as you have done

  • apply a distance transform on the thresholded image

  • threshold the distance transform, so that only a small 'seed' part of each bacterium remains

  • label these seeds, giving each one a different shade of gray
    (also add a labeled seed for the background)

  • execute the watershed algorithm with these seeds and the distance transformed image, to get the separatd contours of your bacteria

Check out the linked answer for some pictures that will make this much clearer.

Community
  • 1
  • 1
HugoRune
  • 13,157
  • 7
  • 69
  • 144
  • Thanks HugoRune for the answer and the link!I've already tried to use watershed but I did not thresholded the distance transform so my seeds were quite big and diverse. Artifact sometimes arose from small unwanted seeds but thresholding the distance transform should help!thanks a lot. – JacoSolari Jul 15 '14 at 08:11
0

A few thoughts:

  1. Otsu may not be a good choice, as you may even use a fixed threshold (your bacteria are black).
  2. Thresholding the image with any method will remove a lot of useful information.

I do not have a complete recipe for you, but even this very simple thing seems to give a lot of interesting information:

import matplotlib.pyplot as plt
import cv2

# cv2 is only used to read the image into an array, use only green channel
bact = cv.imread("/tmp/bacteria.png")[:,:,1]

# draw a contour image with fixed threshold 50
fig = plt.figure()
ax = fig.add_subplot(111)
ax.contourf(bact, levels=[0, 50], colors='k')

This gives:

enter image description here

This suggests that if you use contour-tracing techniques with fixed contours, you will receive quite nice-looking starting points for dilation and erosion. So, two differences in thresholding:

  1. Contouring uses much more of the grayscale information than simple black/white thresholding.
  2. The fixed threshold seems to work well with these images, and if illumination correction is needed, Otsu is not the best choice.
DrV
  • 22,637
  • 7
  • 60
  • 72
  • Thanks a lot for this insights DrV. This is very helpful, I will try to play around with contour tracing and erosion/dilation! – JacoSolari Jul 15 '14 at 08:08
0

One day skimage Watershed segmentation was more useful for me, than any OpenCV samples. It uses some code borrowed from Cellprofiler project (python-based tool for sophisticated cell image analysis). Hint: use Euclidean distance transform from opencv, it's faster than scipy implementation. Also peak_local_max function has distance parameter, which useful for precise single cells distinguishing. I think this function is more robust in finding cell peaks than rude threshold (because intensity of cells may vary).

You can find scipy watershed implementation, but it has weird behavior.

radioxoma
  • 426
  • 5
  • 16