| name | bioimage-analysis |
| description | Microscopy image analysis for cell biology. Cell segmentation (Cellpose, watershed), object tracking (trackpy), morphology quantification, colony counting, colocalization analysis, and cytoskeleton characterization. For pathology WSI use pathml; for flow cytometry use flow-cytometry-analysis. |
| category | biology |
| license | MIT license |
| metadata | {"skill-author":"InkVell Inc."} |
Bioimage Analysis: Microscopy Image Analysis for Cell Biology
Overview
Bioimage Analysis provides computational tools for processing and quantifying microscopy images in cell biology research. From cell segmentation using deep learning (Cellpose) and classical methods (watershed, Otsu thresholding) to object tracking across time-lapse sequences, morphology quantification, colony counting, colocalization analysis, and cytoskeleton characterization. This skill covers the full pipeline from raw microscopy images to quantitative measurements suitable for statistical analysis.
When to Use This Skill
- Segmenting cells or nuclei from brightfield or fluorescence microscopy images
- Tracking cell migration or particle movement in time-lapse sequences
- Quantifying cell morphology (area, eccentricity, circularity, aspect ratio)
- Counting bacterial or mammalian cell colonies on plates
- Analyzing colocalization of proteins from multi-channel fluorescence images
- Characterizing cytoskeleton fiber orientation and alignment
- Quantifying mitochondrial morphology from JC-1 or TMRM staining
- Batch processing multiple fields of view or multi-well plate images
- Preprocessing microscopy images (denoising, background subtraction, contrast enhancement)
Related Skills: For whole-slide pathology image analysis use pathml or histolab. For flow cytometry data use flow-cytometry-analysis. For clinical imaging (MRI, CT) use clinical-imaging.
Installation
uv pip install cellpose scikit-image opencv-python trackpy tifffile aicsimageio numpy pandas matplotlib
Quick Start
from cellpose import models
import skimage.io
import skimage.measure
import numpy as np
image = skimage.io.imread('cells.tif')
model = models.Cellpose(model_type='cyto2')
masks, flows, styles, diams = model.eval(image, diameter=None, channels=[0, 0])
props = skimage.measure.regionprops_table(masks, image,
properties=['label', 'area', 'eccentricity', 'mean_intensity'])
import pandas as pd
df = pd.DataFrame(props)
print(f"Detected {len(df)} cells")
print(df.describe())
Core Capabilities
1. Image Loading & Preprocessing
Load microscopy images from common formats and prepare them for analysis.
import tifffile
import skimage.io
import skimage.filters
import skimage.exposure
import numpy as np
image = tifffile.imread('experiment.tif')
print(f"Shape: {image.shape}, dtype: {image.dtype}")
from aicsimageio import AICSImage
img = AICSImage('multi_dim.czi')
data = img.get_image_data("ZYX", C=0, T=0)
enhanced = skimage.exposure.equalize_adapthist(image, clip_limit=0.03)
denoised = skimage.filters.gaussian(image, sigma=1.0)
from skimage.morphology import white_tophat, disk
background_removed = white_tophat(image, disk(50))
from skimage.filters import median
from skimage.morphology import square
cleaned = median(image, square(3))
2. Cell Segmentation
Segment individual cells using deep learning or classical approaches.
Cellpose (deep learning):
from cellpose import models
model = models.Cellpose(model_type='cyto2')
masks, flows, styles, diams = model.eval(
image,
diameter=None,
channels=[0, 0],
flow_threshold=0.4,
cellprob_threshold=0.0
)
nuc_model = models.Cellpose(model_type='nuclei')
nuc_masks, _, _, _ = nuc_model.eval(image, diameter=None, channels=[0, 0])
print(f"Segmented {masks.max()} cells")
Classical watershed:
import skimage.segmentation
import skimage.feature
import skimage.filters
import skimage.morphology
from scipy import ndimage
thresh = skimage.filters.threshold_otsu(image)
binary = image > thresh
distance = ndimage.distance_transform_edt(binary)
local_max = skimage.feature.peak_local_max(distance, min_distance=20, labels=binary)
markers = np.zeros_like(image, dtype=int)
for i, (r, c) in enumerate(local_max, start=1):
markers[r, c] = i
markers = ndimage.label(markers)[0]
labels = skimage.segmentation.watershed(-distance, markers, mask=binary)
print(f"Segmented {labels.max()} objects")
Adaptive thresholding:
block_size = 51
adaptive_thresh = skimage.filters.threshold_local(image, block_size, offset=10)
binary = image > adaptive_thresh
from skimage.measure import label
labeled = label(binary)
print(f"Found {labeled.max()} connected components")
3. Object Tracking
Track cells or particles across time-lapse frames.
import trackpy as tp
import pandas as pd
frames = tifffile.imread('timelapse.tif')
all_features = []
for t, frame in enumerate(frames):
features = tp.locate(frame, diameter=11, minmass=1000)
features['frame'] = t
all_features.append(features)
features_df = pd.concat(all_features, ignore_index=True)
trajectories = tp.link(features_df, search_range=15, memory=3)
trajectories = tp.filter_stubs(trajectories, threshold=10)
print(f"Tracked {trajectories['particle'].nunique()} objects over {len(frames)} frames")
msd = tp.emsd(trajectories, mpp=0.65, fps=1)
print(msd.head())
4. Morphology Quantification
Extract shape and intensity measurements from segmented objects.
import skimage.measure
import pandas as pd
props = skimage.measure.regionprops_table(masks, intensity_image=image,
properties=[
'label', 'area', 'perimeter', 'eccentricity', 'solidity',
'major_axis_length', 'minor_axis_length', 'mean_intensity',
'max_intensity', 'min_intensity', 'centroid'
])
df = pd.DataFrame(props)
df['circularity'] = 4 * np.pi * df['area'] / (df['perimeter'] ** 2)
df['aspect_ratio'] = df['major_axis_length'] / df['minor_axis_length']
print(f"Measured {len(df)} objects")
print(df[['area', 'eccentricity', 'circularity', 'mean_intensity']].describe())
5. Colony Counting
Count colonies from plate images.
import skimage.io
import skimage.filters
import skimage.segmentation
import skimage.measure
from scipy import ndimage
plate = skimage.io.imread('agar_plate.jpg', as_gray=True)
smoothed = skimage.filters.gaussian(plate, sigma=2)
thresh = skimage.filters.threshold_otsu(smoothed)
binary = smoothed < thresh
from skimage.morphology import remove_small_objects
cleaned = remove_small_objects(binary, min_size=50)
distance = ndimage.distance_transform_edt(cleaned)
local_max = skimage.feature.peak_local_max(distance, min_distance=10, labels=cleaned)
markers = np.zeros_like(plate, dtype=int)
for i, (r, c) in enumerate(local_max, start=1):
markers[r, c] = i
markers = ndimage.label(markers)[0]
separated = skimage.segmentation.watershed(-distance, markers, mask=cleaned)
regions = skimage.measure.regionprops(separated)
colony_count = len(regions)
areas = [r.area for r in regions]
print(f"Colony count: {colony_count}")
print(f"Mean colony area: {np.mean(areas):.1f} px^2")
6. Colocalization Analysis
Quantify spatial overlap between fluorescence channels.
import numpy as np
from scipy.stats import pearsonr
ch1 = skimage.io.imread('green_channel.tif').astype(float)
ch2 = skimage.io.imread('red_channel.tif').astype(float)
mask = (ch1 > skimage.filters.threshold_otsu(ch1)) | \
(ch2 > skimage.filters.threshold_otsu(ch2))
ch1_masked = ch1[mask]
ch2_masked = ch2[mask]
pcc, pval = pearsonr(ch1_masked, ch2_masked)
print(f"Pearson correlation: {pcc:.4f} (p={pval:.2e})")
ch1_coloc = ch1_masked[ch2_masked > 0]
ch2_coloc = ch2_masked[ch1_masked > 0]
M1 = ch1_coloc.sum() / ch1_masked.sum()
M2 = ch2_coloc.sum() / ch2_masked.sum()
print(f"Manders M1: {M1:.4f}, M2: {M2:.4f}")
7. Cytoskeleton & Fiber Analysis
Characterize fiber orientation and alignment.
import skimage.feature
import skimage.transform
import numpy as np
edges = skimage.feature.canny(image, sigma=2)
tested_angles = np.linspace(-np.pi / 2, np.pi / 2, 180, endpoint=False)
h, theta, d = skimage.transform.hough_line(edges, theta=tested_angles)
peaks = skimage.transform.hough_line_peaks(h, theta, d, num_peaks=50)
angles_deg = np.degrees(peaks[1])
mean_angle = np.mean(angles_deg)
std_angle = np.std(angles_deg)
cos2 = np.cos(2 * np.radians(angles_deg))
order_parameter = np.mean(cos2)
print(f"Mean orientation: {mean_angle:.1f} +/- {std_angle:.1f} degrees")
print(f"Order parameter S: {order_parameter:.3f}")
8. Mitochondrial Morphology
Analyze mitochondrial shape and membrane potential.
import skimage.measure
import numpy as np
red_channel = skimage.io.imread('jc1_red.tif').astype(float)
green_channel = skimage.io.imread('jc1_green.tif').astype(float)
ratio = red_channel / (green_channel + 1e-6)
mean_ratio = np.mean(ratio[ratio > 0])
print(f"Mean red/green ratio: {mean_ratio:.3f}")
thresh = skimage.filters.threshold_otsu(green_channel)
mito_mask = green_channel > thresh
mito_labels = skimage.measure.label(mito_mask)
props = skimage.measure.regionprops_table(mito_labels,
properties=['label', 'area', 'major_axis_length', 'minor_axis_length',
'eccentricity', 'euler_number'])
df = pd.DataFrame(props)
df['aspect_ratio'] = df['major_axis_length'] / (df['minor_axis_length'] + 1e-6)
df['morphology'] = pd.cut(df['aspect_ratio'], bins=[0, 2, 4, np.inf],
labels=['round', 'intermediate', 'elongated'])
print(df['morphology'].value_counts())
9. Batch Processing
Process multiple fields of view or wells.
from pathlib import Path
import pandas as pd
image_dir = Path('plate_images/')
all_results = []
for img_path in sorted(image_dir.glob('*.tif')):
image = skimage.io.imread(str(img_path))
model = models.Cellpose(model_type='cyto2')
masks, _, _, _ = model.eval(image, diameter=None, channels=[0, 0])
props = skimage.measure.regionprops_table(masks, image,
properties=['label', 'area', 'eccentricity', 'mean_intensity'])
df = pd.DataFrame(props)
df['image'] = img_path.stem
all_results.append(df)
results = pd.concat(all_results, ignore_index=True)
summary = results.groupby('image').agg(
cell_count=('label', 'count'),
mean_area=('area', 'mean'),
mean_intensity=('mean_intensity', 'mean')
).reset_index()
print(summary)
Typical Workflows
Workflow 1: Segment and Count Cells from Brightfield Microscopy
from cellpose import models
import skimage.io, skimage.measure
import pandas as pd
image = skimage.io.imread('brightfield_cells.tif')
model = models.Cellpose(model_type='cyto2')
masks, _, _, diams = model.eval(image, diameter=None, channels=[0, 0])
props = skimage.measure.regionprops_table(masks, image,
properties=['label', 'area', 'eccentricity', 'mean_intensity', 'centroid'])
df = pd.DataFrame(props)
print(f"Cell count: {masks.max()}")
print(f"Mean area: {df['area'].mean():.1f} px^2")
print(f"Mean eccentricity: {df['eccentricity'].mean():.3f}")
Workflow 2: Track Cell Migration and Compute Velocity
import trackpy as tp
import tifffile
import numpy as np
frames = tifffile.imread('migration_timelapse.tif')
pixel_size = 0.65
dt = 5
features = tp.batch(frames, diameter=15, minmass=500)
tracks = tp.link(features, search_range=20, memory=3)
tracks = tp.filter_stubs(tracks, threshold=10)
velocities = []
for pid, group in tracks.groupby('particle'):
group = group.sort_values('frame')
dx = np.diff(group['x'].values) * pixel_size
dy = np.diff(group['y'].values) * pixel_size
speed = np.sqrt(dx**2 + dy**2) / dt
velocities.append({'particle': pid, 'mean_speed': np.mean(speed),
'max_speed': np.max(speed), 'n_frames': len(group)})
vel_df = pd.DataFrame(velocities)
print(f"Mean migration speed: {vel_df['mean_speed'].mean():.3f} um/min")
Workflow 3: Quantify Protein Colocalization from Dual-Channel Fluorescence
import skimage.io
import numpy as np
from scipy.stats import pearsonr
green = skimage.io.imread('protein_A_green.tif').astype(float)
red = skimage.io.imread('protein_B_red.tif').astype(float)
green -= np.percentile(green, 5)
red -= np.percentile(red, 5)
green = np.clip(green, 0, None)
red = np.clip(red, 0, None)
mask = (green > green.mean()) | (red > red.mean())
g_masked, r_masked = green[mask], red[mask]
pcc, pval = pearsonr(g_masked, r_masked)
M1 = g_masked[r_masked > 0].sum() / g_masked.sum()
M2 = r_masked[g_masked > 0].sum() / r_masked.sum()
print(f"Pearson r = {pcc:.4f} (p = {pval:.2e})")
print(f"Manders M1 = {M1:.4f}, M2 = {M2:.4f}")
Workflow 4: Count Bacterial Colonies on Agar Plate Image
import skimage.io, skimage.filters, skimage.measure
from skimage.morphology import remove_small_objects
from scipy import ndimage
import numpy as np
plate = skimage.io.imread('agar_plate.jpg', as_gray=True)
smoothed = skimage.filters.gaussian(plate, sigma=2)
binary = smoothed < skimage.filters.threshold_otsu(smoothed)
binary = remove_small_objects(binary, min_size=30)
distance = ndimage.distance_transform_edt(binary)
from skimage.feature import peak_local_max
local_max = peak_local_max(distance, min_distance=8, labels=binary)
markers = np.zeros_like(binary, dtype=int)
for i, (r, c) in enumerate(local_max, start=1):
markers[r, c] = i
markers = ndimage.label(markers)[0]
labels = skimage.segmentation.watershed(-distance, markers, mask=binary)
regions = skimage.measure.regionprops(labels)
print(f"Colony count: {len(regions)}")
print(f"Mean area: {np.mean([r.area for r in regions]):.0f} px^2")
Best Practices
- Always inspect images first โ check bit depth, dimensions, and channel order before processing
- Use appropriate preprocessing โ CLAHE for low contrast, Gaussian blur for noisy images, background subtraction for uneven illumination
- Validate segmentation visually โ overlay masks on original images to verify quality before batch processing
- Cellpose model selection โ use
cyto2 for whole-cell, nuclei for nuclear segmentation; adjust diameter parameter if auto-detection fails
- Trackpy parameter tuning โ
diameter must be odd integer; search_range should be < typical inter-particle distance; use memory for blinking objects
- Colocalization controls โ always include single-stained controls; report both Pearson and Manders coefficients
- Batch consistency โ use identical preprocessing and segmentation parameters across all images in an experiment
- Scale bars โ always record pixel size (um/px) from microscope metadata for absolute measurements
Troubleshooting
Problem: Cellpose segments too many or too few cells
Solution: Adjust diameter parameter manually instead of auto-detection. Increase cellprob_threshold (e.g., 0.5) to reduce false positives, decrease for more detections.
Problem: Touching colonies not separated by watershed
Solution: Decrease min_distance in peak_local_max. Try morphological erosion before distance transform.
Problem: Trackpy links wrong particles across frames
Solution: Decrease search_range to limit maximum displacement. Increase minmass to exclude dim objects that cause mislinks.
Problem: TIFF file won't load or has wrong dimensions
Solution: Use tifffile.imread for raw loading. Check image.shape and image.dtype. For multi-series files use aicsimageio.
Problem: Colocalization values seem artificially high
Solution: Ensure proper background subtraction. Use Costes automatic thresholding. Check for bleed-through between channels.
Resources