1
$\begingroup$

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.

Sample 1

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

Raw image

Mask

Mask image


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.

$\endgroup$
7
  • $\begingroup$ How was the mast determined? Manually? if so, can you try to automate your manual process? $\endgroup$ Commented May 19, 2022 at 16:00
  • $\begingroup$ The mask was done manually, yes and automating it is the point of the script @PeterK. ;) but I'd like for the script to yield a better result. Many of the nuclei in the final segmentation are clearly poorly segmented. $\endgroup$ Commented May 20, 2022 at 9:00
  • $\begingroup$ @lanery, Do you have more examples for images? $\endgroup$ Commented May 24, 2022 at 11:55
  • $\begingroup$ Could you add a pair of images and masks? $\endgroup$ Commented May 27, 2022 at 14:08
  • $\begingroup$ @Royi Sure! It was getting cluttered so uploaded them to a zip folder that is divided into "fluorescence" and "mask" images (pairs have the same file names). $\endgroup$ Commented May 27, 2022 at 14:43

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.