Reputation: 3573
I am trying to segment an image of cell nuclei but I end-up with an under-segmented result. Some big blobs appear that ideally should be broken down to smaller objects, like those at the right edge, see below.
Is there anything I could do to handle that please. I would like to split these big cells like the blue one near the middle of the right edge using watershed segmentation (either skimage
or opencv
)
My code so far looks like this:
def segment_dapi(img_in):
img = cv2.cvtColor(img_in, cv2.COLOR_BGR2GRAY)
kernel = np.ones((3, 3), np.uint8)
# set the parameters
thresh = 90
min_size = 5
# Adjust brightness
lims = stretchlim(img)
img_adj = imadjust(img, lims)
# Threshold the image
thres_val = np.percentile(img, thresh)
_, bw_img = cv2.threshold(img_adj, thres_val, 255, cv2.THRESH_BINARY)
# Apply morphology opening to remove small objects
img_obj = cv2.morphologyEx(bw_img, cv2.MORPH_OPEN, kernel, iterations=1)
bg = cv2.dilate(img_obj, kernel, iterations=1) # black points belong to the background
# white points (value = 255) belong to the foreground
dist_transform = cv2.distanceTransform(img_obj, cv2.DIST_L2, 3)
_, fg = cv2.threshold(dist_transform, min_size, 255, cv2.THRESH_BINARY)
fg = np.uint8(fg)
fg_temp = 255/fg.max() * fg
x = cv2.subtract(bg, fg)
_, markers = cv2.connectedComponents(fg)
markers = markers + 1 # prevent the markers from having values = 0
markers[x == 255] = 0
'''
markers:
> 1: absolute foreground
= 1: absolute background
= 0: unknown area (TBD by watershed)
'''
markers = cv2.watershed(img_in, markers)
img_in[markers == -1] = [0, 255, 255]
cv2.imwrite('watershed_borders.tif', img_in);
small_img = cv2.resize(img_in, None, fx=1/2, fy=1/2)
# cv2.imshow('Overlay', small_img)
# cv2.waitKey(0)
'''
markers after watershed:
= 0: background (set by watershed)
= 1: background (because the markers have been shifted by 1)
> 1: object labels
- 1: borders between object
'''
markers[markers>0] = markers[markers>0]-1
markers[markers == -1] = 0
print(markers.max())
overlay = color.label2rgb(markers, bg_label=0)
my_dpi = 72
fig, ax = plt.subplots(figsize=(6000 / my_dpi, 6000 / my_dpi), dpi=my_dpi)
plt.imshow(overlay)
ax.set_axis_off()
plt.tight_layout()
plt.show()
def stretchlim(img):
nbins = 255
tol_low = 0.01
tol_high = 0.99
sz = np.shape(img)
if len(sz) == 2:
img = img[:, :, None]
sz = np.shape(img)
p = sz[2]
ilowhigh = np.zeros([2, p])
for i in range(0,p):
hist,bins = np.histogram(img[:, :, i].ravel(), nbins+1, [0, nbins])
cdf = np.cumsum(hist) / sum(hist)
ilow = np.argmax(cdf > tol_low)
ihigh = np.argmax(cdf >= tol_high)
if ilow == ihigh:
ilowhigh[:, i] = np.array([1, nbins])
else:
ilowhigh[:, i] = np.array([ilow, ihigh])
lims = ilowhigh / nbins
return lims
def imadjust(img, lims):
lims = lims.flatten()
lowIn = lims[0]
highIn = lims[1]
lowOut = 0
highOut = 1
gamma = 1
lut = adjustWithLUT(img, lowIn, highIn, lowOut, highOut, gamma)
out = lut[img].astype(np.uint8)
return out
def adjustWithLUT(img,lowIn,highIn,lowOut,highOut,gamma):
lutLength = 256 # assumes uint8
lut = np.linspace(0, 1, lutLength)
lut = adjustArray(lut, lowIn, highIn, lowOut, highOut, gamma)
lut = img_as_ubyte(lut)
return lut
def adjustArray(img, lIn, hIn, lOut, hOut, g):
# %make sure img is in the range [lIn;hIn]
img = np.maximum(lIn, np.minimum(hIn, img))
out = ((img - lIn) / (hIn - lIn)) ** g
out = out ** (hOut - lOut) + lOut
return out
Upvotes: 0
Views: 591
Reputation: 5738
You could spend a long time finding the exact parameters to segment this nicely, but in my experience it is finicky and then doesn't work for the next image you want to segment. These days I would actually recommend you use a pre-trained deep learning network such as cellpose.
Upvotes: 2