【问题标题】:Extract spherical region in image at a certain position (with skimage et al.)提取图像中某个位置的球面区域(使用 skimage 等人)
【发布时间】:2025-12-15 06:55:01
【问题描述】:

我有(一堆 3D)层析数据堆栈,在这些数据中我推导出了某个(3D)坐标,我需要围绕该坐标切出一个球形区域。

我的代码为我生成了以下图像,该图像概述了我的工作。 我根据白色虚线和绿色虚线区域计算橙色和绿色点。 在这些中间点周围,我想切出一个球形区域,它的表示现在在图像中用一个圆圈标记(也是由我的代码绘制的)。

skimage.morphology.ball 构造一个球体并将其与原始图像相乘很容易,但是如何将球体的中心设置在图像中所需的 3D 位置? 大约 30 个 3D 图像堆栈的大小不同,区域也不同,但我已经准备好所有必要的坐标以供进一步使用。

【问题讨论】:

    标签: python numpy image-processing scikit-image


    【解决方案1】:

    以下是我使用的两种方法,它们展示了对数组中的子区域进行“操作”(以某种方式计算值)的两种方法。两种不同的方法是:

    假设您只想计算球形区域中的值的平均值:

    1 - 将区域坐标直接指定为“切片”data[region_coordinates].mean()

    2 - 使用掩码版本的数组,其中掩码用于指定区域:data_masked.mean()

    哪个可能更好取决于您可能希望对该区域中的值做什么。两者都可以互换使用,您可以选择哪个使您的代码更清晰/更容易/更快。


    在我的工作中,我使用了这两种方法,但更常见的是第一种方法(将区域指定为坐标的“切片”)。

    对我来说,坐标切片方法有优势:

    1 - 发生的事情更加明显

    2 - 如果需要,您可以更轻松地将几何运算应用于您的“区域”。 (例如旋转、平移、缩放……)

    以下是示例代码,以及可用于任一方法的方法:

    mport numpy as np
    import skimage.morphology as mo
    from typing import Tuple
    
    
    def get_ball_coords(radius: int, center: Tuple[int]) -> Tuple[np.ndarray]:
        """
        Use radius and center to return the coordinates within that 3d region
        as a 'slice'.
        """
    
        coords = np.nonzero(mo.ball(radius))
        # 'coords' is a tuple of 1d arrays - to move center using pure numpy, 
        # first convert to a 2d array
        coords_array = np.array(coords)
        center_array = np.array([center]).T
    
        # transform coordinates to be centered at 'center'
        coords_array = coords_array - radius + center_array
        # convert coordinates back to tuple of 1d arrays, which can be used
        # directly as a slice specification
        coords_tuple = (
            coords_array[0,:],
            coords_array[1,:],
            coords_array[2,:]
        )
    
        return coords_tuple
    
    
    def get_masked_array(data: np.ndarray, radius: int, center: Tuple[int]) -> np.ndarray:
        """
        Return a masked version of the data array, where all values are masked
        except for the values within the sphere specified by radius and center.
        """
    
        # get 'ball' as 2d array of booleans
        ball = np.array(mo.ball(radius), dtype=bool)
        # create full mask over entire data array
        mask = np.full_like(data, True, dtype=bool)
        # un-mask the 'ball' region, translated to the 'center'
        mask[
            center[0]-radius: center[0]+radius+1,
            center[1]-radius: center[1]+radius+1,
            center[2]-radius: center[2]+radius+1
        ] = ~ball
        # mask is now True everywhere, except inside the 'ball'
        # at 'center' - create masked array version of data using this.
        masked_data = np.ma.array(data=data, mask=mask)
        return masked_data
    
    
    
    # make some 3D data
    data_size = (100,100,100)
    data = np.random.rand(*data_size)
    
    
    # define some spherical region by radius and center
    region_radius = 2
    region_center = (23, 41, 53)
    
    # get coordinates of this region
    region_coords = get_ball_coords(region_radius, region_center)
    
    # get masked version of the data, based on this region
    data_masked = get_masked_array(data, region_radius, region_center)
    
    # now you can use 'region_coords' as a single 'index' (slice)
    # to specify only the points with those coordinates
    print('\nUSING COORDINATES:')
    print('here is mean value in the region:')
    print(data[region_coords].mean())
    print('here is the total data mean:')
    print(data.mean())
    
    # of you can use the masked array as-is:
    print('\nUSING MASKED DATA:')
    print('here is mean value in the region:')
    print(data_masked.mean())
    print('here is the total data mean:')
    print(data.mean())
    

    【讨论】:

    • 感谢@Richard 的精彩投入。掩码数组是一个特别好的主意。因为无论如何我都将剪切图像保存到磁盘(以便在重新运行管道时更快地处理),所以我会在进一步的步骤中计算来自该区域的必要信息,但是以您建议的方式。
    • 欢迎 - 这是我知道的两种方法。但是可能还有其他人(使用 numpy,我总是会遇到新的做事方式),因此研究和阅读更多内容可能是一个好方法。
    【解决方案2】:
    1. 你有一些半径 r 和一个索引 (i,j,k) 到数据中。

    2. kernel = skimage.morphology.ball(r) 返回一个掩码/内核,每侧为a = 2*r + 1。它是立方体形状的。

    3. 从断层扫描仪中取出一个立方体形状的切片,即内核大小。起始索引取决于您需要中心的位置以及内核的半径。

      piece = data[i-r:i-r+a, j-r:j-r+a, k-r:k-r+a]

    4. 将二进制“球”掩码/内核应用于切片。

      piece *= kernel

    【讨论】:

    • 感谢您的意见!我(我想我)需要保持原始图像的范围,以使后续的遮罩/计算更容易。但这仅仅意味着我必须首先将内核“切片”成一个空的 numpy 数组,然后将其与原始数据相乘。
    • 您可以使用np.mgrid[](是的,它需要切片)来构建和围绕索引数组。如果您需要以某种方式转换它们,这很方便。不过,这取决于您打算做什么。
    • 顺便说一句:图像不是树干,而是小很多的东西;造影剂在老鼠心脏上灌输了“东西”。还没准备好更详细地讨论它,但希望这能尽快出版:)