I'm seeking a better image processing pipeline to essentially segment cell nuclei in fluorescence images. So far, I've tried a bunch of morphological operations, e.g. modifying this scikit-image pipeline. My implementation
import numpy as np
import matplotlib.pyplot as plt
from skimage import io, morphology, filters, color
# Ground truth mask
mask = io.imread('/path/to/mask.png')
mask_rgba = colorize(mask, T_GREEN)
# a) Raw
image_FM = io.imread('/path/to/image_FM.png')
# b) Blur
smooth = filters.gaussian(image_FM, sigma=3)
# c) Opening
structure = morphology.disk(radius=10)
opened = morphology.opening(smooth, selem=structure)
# d) Closing
closed = morphology.closing(opened, selem=structure)
# e) Threshold
otsu = filters.threshold_otsu(closed)
thresh = closed > otsu
# f) Fill in
structure = morphology.disk(radius=20)
filled = morphology.closing(thresh, selem=structure)
# Calculate Intersection over Union
IoU = calc_IoU(filled, mask)
out = f"IoU = {IoU:.1%}"
print(out, '\n' + '-'*len(out))
# Create figure
fig, axes = plt.subplots(ncols=3, nrows=2, figsize=(15, 10))
axes[0, 0].imshow(image_FM, cmap='Greys_r')
axes[0, 1].imshow(smooth, cmap='Greys_r')
axes[0, 2].imshow(opened, cmap='Greys_r')
axes[1, 0].imshow(closed, cmap='Greys_r')
axes[1, 1].imshow(thresh, cmap='Greys_r')
axes[1, 2].imshow(filled, cmap='Greys_r')
axes[1, 2].imshow(mask_rgba, alpha=0.5)
# Aesthetics
titles = ['a) Raw', 'b) Blur', 'c) Opening',
'd) Closing', 'e) Threshold', 'f) Filled in']
[ax.set_title(title) for title, ax in zip(titles, axes.flat)]
axes[0, 0].set_title('a) Raw')
axes[0, 1].set_title('b) Blur')
[ax.axis('off') for ax in axes.flat];
which results in an intersection-over-union (IoU) score of only 67%, which I think could definitely be improved upon.
Would be happy to hear any ideas/suggestions the community might have.
For full reproducibility, the code for the colorize and calc_IoU functions are
def colorize(image, T):
# Convert to rgba
rgba = color.gray2rgba(image, alpha=True)
# Apply transform
transformed = np.dot(rgba, T)
rescaled = exposure.rescale_intensity(transformed)
return rescaled
def calc_IoU(y_true, y_pred):
y_true = y_true > 0.5
y_pred = y_pred > 0.5
overlap = ((y_true == 1) & (y_pred == 1)).sum()
union = ((y_true == 1) | (y_pred == 1)).sum()
IoU = overlap / union
return IoU
and the images:
Raw
Mask
Additional images
Additional sample images are available below.
https://surfdrive.surf.nl/files/index.php/s/GhZB2GkBLQX7aBM
Note that some nuclei may be missed in the mask.


