This notebook may contain text, code and images generated by artificial intelligence. Used model: claude-sonnet-4-6, vision model: claude-sonnet-4-6, fast_model: None, endpoint: None, bia-bob version: 0.35.0.. It is good scientific practice to check the code and results it produces carefully. Read more about code generation using bia-bob

Cell Segmentation Using the Watershed Algorithm#

In this notebook, we will segment cells from a 2D membrane image using the watershed algorithm. The watershed algorithm treats the image as a topographic map and floods it from local minima, creating segmentation boundaries where the flooding from different sources meets.

import numpy as np
import stackview
from skimage.io import imread
from skimage.filters import gaussian
from skimage.morphology import white_tophat, disk
from skimage.segmentation import watershed
from skimage.feature import peak_local_max
from skimage.measure import label
from scipy.ndimage import distance_transform_edt

Step 1: Load the Image#

We load the 2D membrane image from disk and inspect it.

image = imread('data/membrane2d.tif')

stackview.insight(image)
shape(256, 256)
dtypeuint16
size128.0 kB
min277
max44092

Step 2: Top-Hat Background Removal#

We apply a white top-hat filter using skimage.morphology.white_tophat with a disk-shaped structuring element of radius 15. This replaces the pyclesperanto cle.top_hat call and removes uneven background illumination by subtracting the morphological opening from the original image, enhancing bright membrane structures.

footprint = disk(15)

background_corrected = white_tophat(image, footprint=footprint)

stackview.insight(background_corrected)
shape(256, 256)
dtypeuint16
size128.0 kB
min0
max42398

Step 3: Preprocess the Image#

We apply a Gaussian blur to reduce noise in the background-corrected membrane image. This helps improve the quality of subsequent thresholding and distance transform steps.

blurred = gaussian(background_corrected.astype(float), sigma=3)

stackview.insight(blurred)
shape(256, 256)
dtypefloat64
size512.0 kB
min97.52161880577154
max10807.295376916061

Step 4: Threshold the Image to Create a Binary Mask#

We threshold to separate cell interiors from membranes. Since membranes appear bright, the cell interiors will be the dark regions (below the threshold). We invert the binary image so that cell interiors are True.

threshold = 2000

binary = blurred < threshold  # cell interiors are dark

stackview.insight(binary)
C:\structure\code\embl-bia-2026\.venv\Lib\site-packages\stackview\_static_view.py:107: RuntimeWarning: Converting input from bool to <class 'numpy.uint8'> for compatibility.
  h, _ = np.histogram(self, bins=num_bins)
shape(256, 256)
dtypebool
size64.0 kB
minFalse
maxTrue

Step 5: Compute the Distance Transform#

We compute the Euclidean distance transform of the binary mask. Each pixel in the result holds the distance to the nearest background (membrane) pixel. Peaks in this map correspond to cell centers.

distance = distance_transform_edt(binary)

stackview.insight(distance)
shape(256, 256)
dtypefloat64
size512.0 kB
min0.0
max43.01162633521314

Step 6: Detect Local Maxima as Seed Points (Markers)#

We find local maxima in the distance map. These correspond to the centers of the cells and will serve as seed points (markers) for the watershed algorithm.

coords = peak_local_max(distance, min_distance=10, labels=binary)

mask = np.zeros(distance.shape, dtype=bool)

mask[tuple(coords.T)] = True

markers = label(mask)

print('Number of detected seed points (cells):', markers.max())

stackview.insight(markers)
Number of detected seed points (cells): 27
shape(256, 256)
dtypeint32
size256.0 kB
min0
max27
n labels27

Step 7: Apply the Watershed Algorithm#

We apply the watershed algorithm on the inverted distance map (so that peaks become valleys), using the detected markers as seeds and the binary mask to constrain segmentation to cell regions.

labels = watershed(-distance, markers)

print('Number of segmented cells:', labels.max())

stackview.insight(labels)
Number of segmented cells: 27
shape(256, 256)
dtypeint32
size256.0 kB
min1
max27
n labels27

Step 8: Visualize the Result with stackview.animate_curtain#

We use stackview.animate_curtain to display the original membrane image alongside the segmentation label image. This animated curtain view allows us to interactively compare the raw image with the segmented cells.

stackview.animate_curtain(image, labels)

Exercise#

Modify the threshold and the Gaussian blur sigma to improve the segmentation result.