mcpField
S
scikit-image-processing

scikit-image-processing

github.com/jaechang-hits数据与数据库38472 次安装1 周前更新
代码仓库

关于 scikit-image-processing

scikit-image-processing 是使用 scikit-image 和 NumPy 数组进行 Python 图像分析的技能,面向科学及生物图像处理,涵盖预处理、分割、特征检测和测量。

主要功能

  1. 01读写图像文件
  2. 02降噪和图像滤波
  3. 03通过阈值或分水岭分割对象
  4. 04执行形态学操作
  5. 05测量区域属性和强度
  6. 06检测特征并应用变换

适用场景

  1. 01预处理荧光显微图像
  2. 02用分水岭分离相接的细胞核
  3. 03测量细胞面积和荧光强度
  4. 04用统一流程批量处理图像

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/jaechang-hits/sciagent-skills --skill scikit-image-processing

安装后在 Claude Code 中输入/scikit-image-processing确认已加载。

详细介绍

scikit-image — 科学图像处理

概述

scikit-image 是 SciPy 生态系统中的 Python 图像处理库。它提供用于图像读写、滤波(降噪、边缘检测)、几何变换、分割(阈值处理、分水岭、主动轮廓)、对象测量(面积、强度、形状描述符)和特征检测的算法。图像以 NumPy 数组表示,可与 NumPy、SciPy、matplotlib 和 pandas 无缝集成。广泛用于荧光显微镜、组织学和一般生物图像分析。

适用场景

  • 预处理荧光显微镜图像:背景扣除、降噪、照明校正
  • 使用阈值处理或分水岭分割细胞、细胞核或细胞器
  • 测量对象属性:面积、周长、强度统计数据、形状描述符
  • 应用形态学操作:腐蚀、膨胀、开运算、闭运算、填充孔洞
  • 检测生物图像中的关键点或局部特征
  • 在图像格式和色彩空间之间转换
  • 实时视频处理或 GPU 加速操作请改用 OpenCV
  • 深度学习细胞分割请改用 CellPose(对相互接触的细胞准确度更高)
  • 交互式多维图像可视化和标注请改用 napari
  • 全切片图像切块请改用 PathML 或 histolab

前置条件

  • Python 包:scikit-image、numpy、scipy、matplotlib
  • 输入要求:图像文件(TIFF、PNG、JPEG)或 NumPy 数组;荧光图像应为 2D/3D 灰度数组
  • 环境:Python 3.9+
pip install scikit-image numpy scipy matplotlib

# For reading proprietary microscopy formats
pip install tifffile aicsimageio

# Verify
python -c "import skimage; print(skimage.__version__)"

快速入门

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

# Load → denoise → threshold → measure
img = io.imread("cells.tif")
img_smooth = filters.gaussian(img, sigma=1.5)
threshold = filters.threshold_otsu(img_smooth)
binary = img_smooth > threshold

regions = measure.regionprops(measure.label(binary))
print(f"Found {len(regions)} objects")
print(f"Mean area: {np.mean([r.area for r in regions]):.1f} px²")

核心 API

模块 1:图像 I/O 和数据类型
from skimage import io, img_as_float, img_as_uint
import numpy as np

# Read single image
img = io.imread("nuclei.tif")
print(f"Shape: {img.shape}, dtype: {img.dtype}")  # (512, 512), uint16

# Read image collection from directory
from skimage import io as ski_io
images = ski_io.ImageCollection("data/*.tif")
print(f"Loaded {len(images)} images")

# Type conversions (critical for correct arithmetic)
img_f = img_as_float(img)      # uint16 → float64, range [0, 1]
img_u8 = (img_f * 255).astype(np.uint8)  # → 8-bit

# Save image
io.imsave("output.tif", img_u8)
# Multi-channel fluorescence (TIFF with CZYX or ZCYX dims)
import tifffile

stack = tifffile.imread("multichannel.tif")  # shape: (C, Z, Y, X)
dapi = stack[0]   # DAPI channel
gfp = stack[1]    # GFP channel
print(f"DAPI: {dapi.shape}, GFP: {gfp.shape}")

# Maximum intensity projection along Z
mip = dapi.max(axis=0)
io.imsave("dapi_mip.tif", mip)
模块 2:滤波器和预处理
from skimage import filters, restoration
import numpy as np

# Gaussian blur (denoising, smoothing)
from skimage.filters import gaussian
smoothed = gaussian(img, sigma=2.0)

# Median filter (salt-and-pepper noise removal)
from skimage.filters import median
from skimage.morphology import disk
denoised = median(img, footprint=disk(3))

# Top-hat transform (background subtraction for uneven illumination)
from skimage.morphology import white_tophat, disk
background_removed = white_tophat(img, footprint=disk(50))
print(f"Background removed: range [{background_removed.min()}, {background_removed.max()}]")
# Edge detection
from skimage.filters import sobel, laplace, prewitt

edges_sobel = sobel(img_as_float(img))
edges_laplace = laplace(img_as_float(img))

# Difference of Gaussians (blob-like structure detection)
from skimage.filters import difference_of_gaussians
blob_enhanced = difference_of_gaussians(img_as_float(img), low_sigma=1, high_sigma=3)

# Contrast enhancement (CLAHE: local histogram equalization)
from skimage.exposure import equalize_adapthist
enhanced = equalize_adapthist(img_as_float(img), clip_limit=0.03)
模块 3:阈值处理和分割
from skimage import filters, morphology, segmentation
from skimage.color import label2rgb
import numpy as np

# Automatic thresholding methods
from skimage.filters import (threshold_otsu, threshold_li,
                              threshold_triangle, threshold_yen)

img_f = img_as_float(img)
print(f"Otsu: {threshold_otsu(img_f):.3f}")
print(f"Li: {threshold_li(img_f):.3f}")

# Apply threshold and clean binary mask
binary = img_f > threshold_otsu(img_f)
binary_clean = morphology.remove_small_objects(binary, min_size=50)
binary_filled = morphology.remove_small_holes(binary_clean, area_threshold=100)
# Watershed segmentation (separate touching objects)
from skimage.segmentation import watershed
from skimage.feature import peak_local_max
from scipy import ndimage as ndi

# Distance transform → local maxima → watershed
distance = ndi.distance_transform_edt(binary_filled)
coords = peak_local_max(distance, min_distance=20, labels=binary_filled)
mask = np.zeros(distance.shape, dtype=bool)
mask[tuple(coords.T)] = True
markers = ndi.label(mask)[0]
labels = watershed(-distance, markers, mask=binary_filled)

print(f"Segmented objects: {labels.max()}")
overlay = label2rgb(labels, image=img_f, bg_label=0)
模块 4:形态学操作
from skimage.morphology import (erosion, dilation, opening, closing,
                                 disk, ball, binary_erosion, binary_dilation)

# Erosion and dilation
eroded = erosion(binary, footprint=disk(3))
dilated = dilation(binary, footprint=disk(5))

# Opening: erosion then dilation (removes small objects, smooths edges)
opened = opening(binary, footprint=disk(3))

# Closing: dilation then erosion (fills small holes)
closed = closing(binary, footprint=disk(5))

# Skeletonization
from skimage.morphology import skeletonize
skeleton = skeletonize(binary)
print(f"Skeleton pixels: {skeleton.sum()}")
模块 5:测量和区域属性
from skimage import measure
import pandas as pd

# Label connected components
labeled = measure.label(binary_filled)

# Extract region properties
props = measure.regionprops(labeled, intensity_image=img_as_float(img))

# Convert to DataFrame
data = []
for r in props:
    data.append({
        "label": r.label,
        "area": r.area,
        "perimeter": r.perimeter,
        "eccentricity": r.eccentricity,
        "mean_intensity": r.mean_intensity,
        "max_intensity": r.max_intensity,
        "centroid_y": r.centroid[0],
        "centroid_x": r.centroid[1],
        "bbox": r.bbox,
    })

df = pd.DataFrame(data)
print(f"Objects: {len(df)}")
print(df[["area", "mean_intensity", "eccentricity"]].describe().round(2))
# Filter by property thresholds
cells = df[(df["area"] > 100) & (df["area"] < 5000) & (df["eccentricity"] < 0.9)]
print(f"Valid cells: {len(cells)}")

# Measure co-localization: fraction of channel-1 signal in channel-2 positive mask
from skimage.measure import regionprops_table
import numpy as np

# For multi-channel images
table = regionprops_table(
    labeled, intensity_image=np.stack([dapi, gfp], axis=-1),
    properties=["label", "area", "mean_intensity"]
)
模块 6:特征检测和变换
from skimage.feature import blob_log, blob_dog, corner_harris, corner_peaks
from skimage import transform

# Laplacian of Gaussian blob detection (nuclei, puncta)
blobs = blob_log(img_as_float(img), min_sigma=5, max_sigma=20,
                  num_sigma=5, threshold=0.05)
print(f"Blobs detected: {len(blobs)}")
# blobs columns: [y, x, sigma] where radius = sqrt(2) * sigma

# Difference of Gaussians (faster alternative)
blobs_dog = blob_dog(img_as_float(img), min_sigma=5, max_sigma=20, threshold=0.02)
# Geometric transforms
from skimage import transform

# Rescale
img_small = transform.rescale(img_as_float(img), 0.5)

# Rotate
img_rotated = transform.rotate(img_as_float(img), angle=15, resize=True)

# Affine registration (align two images)
from skimage.registration import phase_cross_correlation
shift, error, _ = phase_cross_correlation(ref_img, moving_img)
print(f"Alignment shift: {shift} px, error: {error:.4f}")

核心概念

图像数组和约定

scikit-image 使用 NumPy 数组表示图像。形状约定如下:

图像类型形状dtype
2D 灰度图像(H, W)uint8、uint16、float64
RGB 彩色图像(H, W, 3)uint8
多通道图像(H, W, C)任意
Z 轴堆栈(Z, H, W)任意

dtype 很重要:大多数算法要求使用范围为 [0, 1] 的 float64。处理前使用 img_as_float(img);保存时使用 img_as_uint(img) 转换回来。

常见工作流

工作流 1:荧光细胞分割和测量

目标:分割 DAPI 染色的细胞核,并测量每个细胞核的 GFP 荧光强度。

from skimage import io, filters, morphology, measure, img_as_float
from skimage.segmentation import watershed
from skimage.feature import peak_local_max
from scipy import ndimage as ndi
import pandas as pd
import numpy as np
import tifffile

# Load 2-channel image (DAPI=ch0, GFP=ch1)
img = tifffile.imread("cells.tif")
dapi = img_as_float(img[0])
gfp = img_as_float(img[1])

# Segment nuclei from DAPI channel
dapi_smooth = filters.gaussian(dapi, sigma=2)
threshold = filters.threshold_otsu(dapi_smooth)
binary = dapi_smooth > threshold
binary = morphology.remove_small_objects(binary, min_size=200)
binary = morphology.remove_small_holes(binary, area_threshold=500)

# Watershed to separate touching nuclei
distance = ndi.distance_transform_edt(binary)
coords = peak_local_max(distance, min_distance=30, labels=binary)
mask = np.zeros_like(distance, dtype=bool)
mask[tuple(coords.T)] = True
markers = ndi.label(mask)[0]
labels = watershed(-distance, markers, mask=binary)

# Measure GFP per nucleus
props = measure.regionprops(labels, intensity_image=gfp)
df = pd.DataFrame([{
    "nucleus_id": p.label,
    "area_px2": p.area,
    "gfp_mean": p.mean_intensity,
    "gfp_max": p.max_intensity,
} for p in props])

df.to_csv("nucleus_measurements.csv", index=False)
print(f"Nuclei: {len(df)}, mean GFP: {df['gfp_mean'].mean():.3f}")
工作流 2:批量图像处理

目标:对文件夹中的图像应用相同的预处理和测量流程。

from pathlib import Path
from skimage import io, filters, measure, img_as_float, morphology
import pandas as pd

results = []
for img_path in sorted(Path("data/").glob("*.tif")):
    img = img_as_float(io.imread(img_path))
    if img.ndim == 3:
        img = img.mean(axis=-1)  # convert RGB to grayscale

    # Preprocess
    smooth = filters.gaussian(img, sigma=1.5)
    thresh = filters.threshold_otsu(smooth)
    binary = morphology.remove_small_objects(smooth > thresh, min_size=50)

    # Measure
    labeled = measure.label(binary)
    props = measure.regionprops(labeled, intensity_image=img)
    for p in props:
        results.append({
            "image": img_path.stem,
            "object_id": p.label,
            "area": p.area,
            "mean_intensity": p.mean_intensity,
        })

df = pd.DataFrame(results)
df.to_csv("batch_results.csv", index=False)
print(f"Processed {df['image'].nunique()} images, {len(df)} objects total")

关键参数

函数参数默认值范围/选项效果
gaussiansigma1.00.5–10+平滑核大小;数值越大,模糊程度越高
medianfootprintdisk(1)disk(1–10)中值滤波器的邻域
threshold_otsu———返回自动阈值(无需调参)
remove_small_objectsmin_size64任意整数移除小于 N 个像素的对象
watershed———使用 peak_local_max 提供的标记位置
peak_local_maxmin_distance15–50检测到的峰之间的最小间距(px)
blob_logmin_sigma / max_sigma1/50取决于情况预期斑点半径范围
regionpropsintensity_imageNone数组用于强度测量的图像

最佳实践

  1. 处理前始终检查 dtype:对 uint8 执行减法等操作会静默地将结果截断为 0。先转换为浮点数:img = img_as_float(img)。

  2. 可视化中间结果:对于每个分割流程,在测量前都要将二值掩膜叠加到原图上进行绘制。静默的分割错误是最常见的失败原因。

  3. 使用具有代表性的样本调整阈值:对于双峰直方图,Otsu 效果良好。对于较难处理的图像,可使用 skimage 中的 try_all_threshold(img) 比较 Otsu/Li/Triangle。

  4. 使用分水岭分割相接的对象:简单阈值法无法分开相接的细胞核。对于密集排列的细胞,始终使用距离变换标记应用分水岭算法。

  5. 使用物理单位进行测量:使用显微镜元数据中的像素大小,将像素测量值转换为微米:area_um2 = area_px * pixel_size_um**2。

  6. 使用已知样本进行验证:批量处理前,先用 3–5 张经过人工计数的图像验证流程。抽查对象计数是否符合预期。

常见用法

用法:测量荧光图像中的斑点强度
from skimage import io, filters, measure, img_as_float
from skimage.feature import blob_log
import numpy as np

img = img_as_float(io.imread("spots.tif"))
blobs = blob_log(img, min_sigma=2, max_sigma=8, num_sigma=5, threshold=0.05)

intensities = []
for y, x, sigma in blobs:
    r = int(np.ceil(np.sqrt(2) * sigma))
    region = img[max(0,int(y-r)):int(y+r), max(0,int(x-r)):int(x+r)]
    intensities.append({"y": y, "x": x, "radius": r, "mean_intensity": region.mean()})

import pandas as pd
df = pd.DataFrame(intensities)
print(f"Spots: {len(df)}, mean intensity: {df['mean_intensity'].mean():.4f}")
用法:从多个通道生成二值掩膜
from skimage import filters, morphology, img_as_float
import numpy as np

# Segment objects positive in BOTH channels
ch1 = img_as_float(dapi)
ch2 = img_as_float(gfp)

mask1 = ch1 > filters.threshold_otsu(ch1)
mask2 = ch2 > filters.threshold_otsu(ch2)
combined_mask = mask1 & mask2  # objects in both channels

combined_mask = morphology.binary_closing(combined_mask)
print(f"Co-positive pixels: {combined_mask.sum()}")
用法:将带标签的叠加图保存为出版级图片
from skimage.color import label2rgb
from skimage import io, img_as_ubyte
import matplotlib.pyplot as plt

overlay = label2rgb(labels, image=img_as_float(dapi), bg_label=0, alpha=0.5)

fig, axes = plt.subplots(1, 2, figsize=(12, 6))
axes[0].imshow(dapi, cmap="gray")
axes[0].set_title(f"DAPI (raw)")
axes[1].imshow(overlay)
axes[1].set_title(f"Segmented ({labels.max()} nuclei)")
for ax in axes:
    ax.axis("off")
plt.tight_layout()
plt.savefig("segmentation_overlay.png", dpi=300, bbox_inches="tight")
print("Saved segmentation_overlay.png")

故障排除

问题原因解决方案
算术运算中出现 OverflowErroruint8/uint16 运算发生整数溢出先转换为浮点数:img = img_as_float(img)
Otsu 阈值法只找到一个类别不存在双峰分布尝试 threshold_li() 或 try_all_threshold(img) 进行比较
分水岭过度分割标记点距离过近增大 peak_local_max 中的 min_distance;平滑距离图
二值图像中的所有对象都合并在一起阈值过低使用 plt.hist(img.ravel()) 检查直方图;手动调整
regionprops 缺少强度统计数据未提供 intensity_image传入:regionprops(labels, intensity_image=img)
3D 图像被作为 2D 图像处理未检测到 Z-stack检查形状:img.shape;按切片处理或使用 3D 函数
二值图像中有微小噪声对象阈值过于激进应用 morphology.remove_small_objects(binary, min_size=50)

相关技能

  • pathml — 使用底层 scikit-image 处理全切片图像
  • matplotlib-scientific-plotting — 可视化分割结果和测量分布
  • histolab-wsi-processing — 与 scikit-image 兼容的 H&E WSI 图块预处理

参考资料

评论

支持 Markdown