You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Z-stack二维图像在Python中构建细胞3D模型及计数的技术问询

Great question! Working with Z-stack microscopy images for 3D cell counting is such a common (and tricky) task in bioimage analysis—let’s walk through a practical, Python-based workflow that includes how Delaunay triangulation fits in, plus some tried-and-true tools to make this easier.

Practical Python Workflow for 3D Cell Counting from Z-Stack Images

1. First: Preprocess Your Z-Stack to Clean Up Noise & Background

Raw microscopy images often have uneven background and noise, which can throw off segmentation later. Here’s a quick preprocessing pipeline using scikit-image:

import numpy as np
from skimage import io, filters, restoration

# Load your Z-stack (replace with your file path—TIFF stacks work great here)
z_stack = io.imread("your_cell_z_stack.tif")

# Process each slice to reduce noise and subtract background
processed_stack = []
for slice_img in z_stack:
    # Median filter to knock out salt-and-pepper noise
    denoised = filters.median(slice_img)
    # Rolling ball algorithm to subtract uneven background (adjust radius to your image)
    background = restoration.rolling_ball(denoised, radius=50)
    corrected_slice = denoised - background
    processed_stack.append(corrected_slice)

processed_stack = np.array(processed_stack)

2. 3D Cell Segmentation: The Make-or-Break Step

Accurate cell counting starts with correctly separating cells from the background (and each other). For most cases, using a pre-trained deep learning model is way more reliable than manual thresholding. CellPose is a fantastic tool specifically built for cell segmentation, and it supports 3D Z-stacks out of the box:

from cellpose import models, core

# Initialize the 3D CellPose model (uses "cyto3" for general cell types)
model = models.Cellpose(model_type="cyto3")
# Use GPU if available (set to False if you only have CPU)
use_gpu = core.use_gpu()

# Run segmentation on your processed stack
masks, flows, styles, diams = model.eval(processed_stack, diameter=None, do_3D=True)

The masks output is a 3D array where each connected cell has a unique integer label (background is 0). This is your foundation for counting.

3. Using Delaunay Triangulation for Validation & Refinement

You mentioned Delaunay triangulation—while it’s not the core method for counting, it’s a useful tool to validate your segmentation and fix edge cases (like overlapping cells that the model might split incorrectly). Here’s how to use it:

Step 3.1: Extract Cell Centroids

First, get the 3D center coordinates of each segmented cell:

from skimage.measure import regionprops_table

# Pull out centroids (Z, Y, X coordinates) for each cell
props = regionprops_table(masks, properties=("centroid",))
centroids = np.column_stack([props["centroid-0"], props["centroid-1"], props["centroid-2"]])

Step 3.2: 3D Delaunay Triangulation

Triangulate the centroids to find spatially adjacent cells. This helps you spot cells that are unnaturally close (likely false splits or overlapping cells):

from scipy.spatial import Delaunay

# Perform 3D Delaunay triangulation on the centroids
tri = Delaunay(centroids)

# For each cell, find its neighboring cells via the triangulation
neighbor_indices = []
for cell_idx in range(len(centroids)):
    # Find all simplices (3D triangles) that include this cell's centroid
    simplices_with_cell = tri.simplices[np.any(tri.simplices == cell_idx, axis=1)]
    # Collect unique neighboring cells (exclude the cell itself)
    neighbors = np.unique(simplices_with_cell[simplices_with_cell != cell_idx])
    neighbor_indices.append(neighbors)

Step 3.3: Refine Count Based on Neighbor Distance

Use the triangulation results to filter out false positives. If two cells are closer than a threshold (based on your cell size), they’re likely the same cell:

# Start with the raw count from segmentation
cell_count = len(centroids)
# Adjust this distance threshold based on your microscope's resolution and cell size
min_valid_distance = 6  # Example value in pixels

for cell_idx, neighbors in enumerate(neighbor_indices):
    for neighbor_idx in neighbors:
        # Calculate 3D distance between centroids
        dist = np.linalg.norm(centroids[cell_idx] - centroids[neighbor_idx])
        if dist < min_valid_distance:
            # Subtract one from count (since this is likely a split cell)
            cell_count -= 1
            break  # Avoid double-counting the same overlap

print(f"Refined cell count after Delaunay validation: {cell_count}")

4. The Simplest (and Most Reliable) Counting Method

If your segmentation is solid, you don’t even need Delaunay—just count the unique labels in your mask:

# Get all unique labels, exclude the background (0)
unique_cell_labels = np.unique(masks)
unique_cell_labels = unique_cell_labels[unique_cell_labels != 0]
direct_count = len(unique_cell_labels)

print(f"Direct cell count from segmentation: {direct_count}")

Delaunay is just a bonus for refining counts when you have ambiguous cases.

Pro Tips for Success

  • Check Z-Axis Resolution: Microscope Z-steps are often larger than XY pixels. Scale your Z-coordinates by the step size (e.g., if Z-step is 2μm and XY is 1μm, multiply centroid Z-values by 2) when calculating real-world distances.
  • Visualize Everything: Use napari to interactively view your Z-stack, segmentation masks, and centroids—it’s the best way to catch mistakes:
    import napari
    
    viewer = napari.Viewer()
    viewer.add_image(processed_stack, name="Cleaned Z-Stack")
    viewer.add_labels(masks, name="Segmented Cells")
    viewer.add_points(centroids, size=5, name="Cell Centroids")
    napari.run()
    
  • Try Other Segmentation Tools: If CellPose doesn’t work for your cell type, check out StarDist (great for round cells) or 3D U-Net (if you have labeled data to train your own model).

内容的提问来源于stack exchange,提问作者aleksej stevanovic

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 06:55:53