在特定位置提取图像中的球形区域(使用 skimage 等)

Extract spherical region in image at a certain position (with skimage et al.)

我有(一堆 3D)断层扫描数据堆栈,在这些数据中我已经推导出某个(3D)坐标,我需要围绕它切出一个球形区域。

我的代码生成了下图,概述了我所做的事情。 我根据白色虚线和绿色虚线区域计算橙色和绿色点。 在这些的中点附近,我想切出一个球形区域,它的表示现在在图像中用一个圆圈标记(也是我的代码绘制的)。

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

  1. 你有一些半径 r 和数据的索引 (i,j,k)

  2. kernel = skimage.morphology.ball(r) returns 一个 mask/kernel 每边 a = 2*r + 1。这是 cube-shaped.

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

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

  4. 将二进制“球”mask/kernel应用于切片。

    piece *= kernel

这是我使用的两种方法,它们显示了数组中 sub-regions 的两种 'operating'(以某种方式计算值)的方法。两种不同的方法是:

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

1 - 直接将区域坐标指定为'slice'data[region_coordinates].mean()

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

哪个可能更好取决于您可能希望对该区域中的值执行的操作。两者都可以使用 inter-changebly,您可以只选择使您的代码 clearer/easier/faster.


在我的工作中,我使用这两种方法,但更常用的是第一种方法(您将区域指定为 'slice' 坐标)。

对我来说,坐标切片方法有以下优点:

1 - 发生的事情更加明显

2 - 如果需要,您可以更轻松地将几何运算应用于 'region'。 (例如旋转、平移、缩放...)

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

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())