不幸的是,我找不到任何关于这个话题的东西,所以这里是:
我有一个图像作为numpy数组,包含不同细胞核的掩码作为整数,看起来像这样:
https://i.stack.imgur.com/nn8hG.png
单个蒙版有不同的值,背景为0。现在,对于该图像中的每个遮罩,我想获得其他触摸遮罩的身份(如果有的话)。到目前为止,我所拥有的是获得每个掩码值的像素位置的代码(通过argwhere函数),并检查8个周围像素中的任何像素是否不为0或其自己的值。
for i in range(1, np.max(mask_image+1)):
coordinates = np.argwhere(mask_image==i)
touching_masks = []
for pixel in coordinates:
if mask_image[pixel[0] + 1, pixel[1]] != 0 and mask_image[pixel[0] + 1, pixel[1]] != i:
touching_masks.append(mask_image[pixel[0] + 1, pixel[1]]) #bottom neighbour
elif mask_image[pixel[0] -1, pixel[1]] != 0 and mask_image[pixel[0] -1, pixel[1]] != i:
touching_masks.append(mask_image[pixel[0] -1, pixel[1]]) #top neighbour
elif mask_image[pixel[0], pixel[1]-1] != 0 and mask_image[pixel[0], pixel[1]-1] != i:
touching_masks.append(mask_image[pixel[0], pixel[1]-1]) #left neighbour
elif mask_image[pixel[0], pixel[1] + 1] != 0 and mask_image[pixel[0], pixel[1] + 1] != i:
touching_masks.append(mask_image[pixel[0], pixel[1] + 1]) #right neighbour
elif mask_image[pixel[0] + 1, pixel[1] + 1] != 0 and mask_image[pixel[0] + 1, pixel[1] + 1] != i:
touching_masks.append(mask_image[pixel[0] + 1, pixel[1] + 1]) #bottom-right neighbour
elif mask_image[pixel[0] - 1, pixel[1] - 1] != 0 and mask_image[pixel[0] - 1, pixel[1] - 1] != i:
touching_masks.append(mask_image[pixel[0] - 1, pixel[1] - 1]) #top-left neighbour
elif mask_image[pixel[0] + 1, pixel[1] - 1] != 0 and mask_image[pixel[0] + 1, pixel[1] - 1] != i:
touching_masks.append(mask_image[pixel[0] + 1, pixel[1] - 1]) #bottom-left neighbour
elif mask_image[pixel[0] - 1, pixel[1] + 1] != 0 and mask_image[pixel[0] - 1, pixel[1] + 1] != i:
touching_masks.append(mask_image[pixel[0] - 1, pixel[1] + 1]) #top-right neighbour
由于我每张图像大约有500个遮罩和大约200个图像的时间序列,这是非常慢的,我想改进它。我尝试了一些regionprops和skimage。分割和scipy,但没有找到合适的功能。
我想知道是否
- 已经有一个预先存在的功能可以做到这一点(我盲目地忽略了)
- 只能保留argwhere函数的位置,这些位置是掩码的边界像素,从而减少用于检查周围8个像素的输入像素的数量。条件是这些边界像素始终保留其原始值作为标识符的形式。
任何建议都是非常感谢的!
关于我为什么要这样做的更多背景信息:
我目前正在获取多个细胞在不同小时内的时间周期。有时,在细胞分裂后,两个子核粘在一起,可以分裂成一个核,也可以分裂成两个核。这种情况很少发生,但我想过滤掉这种在一个或两个掩模之间交替的细胞的时间轨迹。我也计算了这些单元格的面积,但是过滤不合理的掩模面积变化会遇到两个问题:
- 徘徊在图像内(或外)的单元格也可以显示这样的大小变化和
- 显微镜的失焦也会导致更小的掩模(当再次达到适当的焦点时,会导致更大的掩模)。不幸的是,在这段时间内,我们的显微镜也会时不时地发生这种情况。我的想法是在整个时间间隔中获得触摸口罩的身份,以便在过滤掉此类细胞时考虑更多标准。
skimage.graph.pixel_graph
函数将告诉您图像中的哪些像素连接到其他像素。你可以用这个图表来回答你的问题——我想得很快。
(请注意,您共享的图像不是分割蒙版,而是值为[0,255]的RGBA灰度图像,所以我不能在下面的分析中使用它。)
步骤1:我们构建像素图。为此,我们只想保留两个标签不同的边,所以我们传入一个边函数,当值不同时返回1.0,否则返回0.0。
import numpy as np
from skimage.graph import pixel_graph
def different_labels(center_value, neighbor_value, *args):
return (center_value != neighbor_value).astype(float)
label_mask = ... # load your image here
g, nodes = pixel_graph(
label_mask,
mask=label_mask.astype(bool),
connectivity=2, # count diagonals in 2D
)
现在,您需要知道g是scipy.sparse.csr_matrix
格式,并且行索引对应于图像中的所有非零像素。为了回到实际的图像位置,您需要nodes
数组,它包含从矩阵索引到像素索引的映射。
矩阵还包含我们不关心的像素的所有零条目,因此使用scipy.sparse.csr_matrix.eliminate_zeros()
方法去除它们。
为了获得不同像素对,我们将矩阵转换为COO矩阵,然后我们获取相应的图像坐标并获取值:
g.eliminate_zeros()
coo = g.tocoo()
center_coords = nodes[coo.row]
neighbor_coords = nodes[coo.col]
center_values = label_mask.ravel()[center_coords]
neighbor_values = label_mask.ravel()[neighbor_coords]
现在我们有(i, j)对相互接触的原子核。(它们有些随意地排列成中心/邻居。同样,这对同时以(i, j)和(j, i)的形式出现。你可以对这些数组做任何你想做的事情,例如,将它们保存到一个文本文件中:
touching_masks = np.column_stack((center_values, neighbor_values))
np.savetxt('touching_masks.txt', touching_masks, delimiter=',')
或创建一个字典,将每个原子核映射到相邻的原子核列表:
from collections import defaultdict
pairs = defaultdict(list)
for i, j in zip(center_values, neighbor_values):
pairs[i].append(j)
一般来说,最好尽量避免遍历像素,而使用NumPy矢量化操作。pixel_graph
函数的源代码可以为如何考虑这类问题提供进一步的启发!
您可以通过例如使用skimage.segmentation中的find_boundaries()函数来查找掩码的共享边界。遗憾的是,这也将包括背景边界,但我们可以过滤掉,通过xor与所有前景像素的蒙版。
a = find_boundaries(mask_image)
b = find_boundaries(mask_image != 0)
touching_masks = np.logical_xor(a, b)
在我的电脑上,1000x1000的图像和500个蒙版大约需要0.05秒。
如果你想要遮罩的值,你可以取
mask_values = mask_image.copy()
mask_values[~touching_masks] = 0
并使用您的代码查找相邻的值。