3D图像分割#

3D图像分割因为几个原因而具有挑战性:在许多显微成像技术中,图像质量在空间上有所变化:例如,当你在样本内部深入成像时,强度和/或对比度会降低。此外,接触的细胞核很难以自动化的方式进行区分。最后但同样重要的是,根据所应用的算法和各自给定的参数,各向异性很难处理。一些算法,如这里演示的Voronoi-Otsu-Labeling方法,只适用于各向同性数据。

from skimage.io import imread
from pyclesperanto_prototype import imshow
import pyclesperanto_prototype as cle
import matplotlib.pyplot as plt

import napari
from napari.utils import nbscreenshot

# For 3D processing, powerful graphics
# processing units might be necessary
cle.select_device('TX')
<NVIDIA GeForce GTX 1650 with Max-Q Design on Platform: NVIDIA CUDA (1 refs)>

为了演示工作流程,我们使用来自Broad Bio Image Challenge的裁剪和重采样的图像数据: Ljosa V, Sokolnicki KL, Carpenter AE (2012). Annotated high-throughput microscopy image sets for validation. Nature Methods 9(7):637 / doi. PMID: 22743765 PMCID: PMC3627348. 可在 http://dx.doi.org/10.1038/nmeth.2083 获取

input_image = imread("../../data/BMP4blastocystC3-cropped_resampled_8bit.tif")

voxel_size_x = 0.202
voxel_size_y = 0.202
voxel_size_z = 1

为了可视化目的,我们展示沿X、Y和Z方向的强度投影。

def show(image_to_show, labels=False):
    """
    这个函数生成三个投影:X、Y和Z方向,并显示它们。
    """
    projection_x = cle.maximum_x_projection(image_to_show)
    projection_y = cle.maximum_y_projection(image_to_show)
    projection_z = cle.maximum_z_projection(image_to_show)

    fig, axs = plt.subplots(1, 3, figsize=(15, 15))
    cle.imshow(projection_x, plot=axs[0], labels=labels)
    cle.imshow(projection_y, plot=axs[1], labels=labels)
    cle.imshow(projection_z, plot=axs[2], labels=labels)
    plt.show()

show(input_image)
print(input_image.shape)
../_images/0a2cddd15382c1c2c1470813f4265e0c6f9f480dcfa1902ae24c92caaa8daade.png
(86, 396, 393)

显然,体素大小不是各向同性的。因此,我们使用体素大小作为缩放因子来缩放图像,以获得具有各向同性体素的图像堆栈。

resampled = cle.scale(input_image, factor_x=voxel_size_x, factor_y=voxel_size_y, factor_z=voxel_size_z, auto_size=True)

show(resampled)
print(resampled.shape)
../_images/58f72b6777f9ce1e481d6de34b7562d1febc7092946fe83d283cc4e8cefa120a.png
(86, 79, 79)

强度和背景校正#

正如我们所看到的,强度在Z方向(从切片到切片)减弱,对比度也是如此。至少强度衰减可以被校正。在CLIJx中,这种方法被称为equalize_mean_intensities_of_slices.

equalized_intensities_stack = cle.create_like(resampled)
a_slice = cle.create([resampled.shape[1], resampled.shape[0]])

num_slices = resampled.shape[0]
mean_intensity_stack = cle.mean_of_all_pixels(resampled)

corrected_slice = None
for z in range(0, num_slices):
    # 从堆栈中获取单个切片
    cle.copy_slice(resampled, a_slice, z)
    # 测量其强度
    mean_intensity_slice = cle.mean_of_all_pixels(a_slice)
    # 校正强度
    correction_factor = mean_intensity_slice/mean_intensity_stack
    corrected_slice = cle.multiply_image_and_scalar(a_slice, corrected_slice, correction_factor)
    # 将切片复制回堆栈中
    cle.copy_slice(corrected_slice, equalized_intensities_stack, z)

show(equalized_intensities_stack)
../_images/0ff9ddcbd0db72ddd57b31a905ef3a00f68c9171121a0835e7dbc05d91fc02f4.png

此外,背景强度似乎在增加,这可能是样本深处散射增加的结果。我们可以通过使用背景减除技术来补偿这一点:

backgrund_subtracted = cle.top_hat_box(equalized_intensities_stack, radius_x=5, radius_y=5, radius_z=5)
show(backgrund_subtracted)
../_images/00a8a05d6025d67a555f336b09aaaeeef8c4b66680392c49584aff62462a10da.png

分割#

segmented = cle.voronoi_otsu_labeling(backgrund_subtracted, spot_sigma=3, outline_sigma=1)
show(segmented, labels=True)
../_images/064562d799ff01029b225d87b88e6f9983e7834cc72ad7e532ece7f9dae09b57.png

由于3D分割结果难以检查,我们生成一个包含原始强度 + 分割轮廓的图像堆栈。我们展示这个堆栈的几个切片。

a_slice = cle.create([resampled.shape[1], resampled.shape[0]])
segmented_slice = cle.create([resampled.shape[1], resampled.shape[0]])

for z in range(0, resampled.shape[2], 20):
    label_outlines = None
    combined = None

    # 从强度图像和分割标签图像中获取单个切片
    cle.copy_slice(resampled, a_slice, z)
    cle.copy_slice(segmented, segmented_slice, z)

    # 确定标记对象周围的轮廓
    label_outlines = cle.detect_label_edges(segmented_slice, label_outlines)

    # 合并两个图像
    outline_intensity_factor = cle.maximum_of_all_pixels(a_slice)
    combined = cle.add_images_weighted(a_slice, label_outlines, combined, 1.0, outline_intensity_factor)

    # 可视化
    fig, axs = plt.subplots(1, 3, figsize=(15, 15))
    cle.imshow(a_slice, plot=axs[0])
    cle.imshow(segmented_slice, plot=axs[1], labels=True)
    cle.imshow(combined, plot=axs[2])
../_images/ea0964200ca75e5cf1e5d7696169a7f6cd01d81474357fa098c3606a5e047b6d.png ../_images/f4c25939efaaa0f4a0fe1783ee7dfd02a2f4e134e0c9c57b32974ec735a7e55b.png ../_images/d9c0f0ce35e5d4fa4b33a23d77a5312d1769bfa2fd6538ae5ca0ee03c8b5b65d.png ../_images/b7340fef733850571838662755952cc6ef6a79af74ccfb36b25afb0c5f5dc50f.png

3D可视化#

对于实际的3D可视化,你也可以使用napari

# 启动napari
viewer = napari.Viewer()

# 显示图像
viewer.add_image(cle.pull(resampled))
viewer.add_image(cle.pull(equalized_intensities_stack))
viewer.add_labels(cle.pull(segmented))
INFO:xmlschema:Resource 'XMLSchema.xsd' is already loaded
<Labels layer 'Labels' at 0x1eba6d51dc0>
viewer.dims.current_step = (40, 0, 0)
nbscreenshot(viewer)
INFO:OpenGL.acceleratesupport:No OpenGL_accelerate module loaded: No module named 'OpenGL_accelerate'

我们可以通过点击左下角的3D按钮切换到3D视图。

nbscreenshot(viewer)

然后我们还可以调整和倾斜视图。

nbscreenshot(viewer)