r 中具有邻域策略的空间聚类 3d 数组

spatial clustering 3d array with neighbourhood strategy in r

我需要使用邻域策略在 3d 数组中执行空间聚类。更清楚地说:我有一个表示为稀疏 3d 数组的 3d 图像。有些实例是 1,而大多数是 0。我想将彼此相邻的等于 1 的实例聚集在一起(即,如果我们将每个实例想象成一个立方体,我想将共享一个实例的实例聚集在一起面、边或角,等于 1)。

我需要在 R 中执行此操作,因为此步骤是较长的机器学习管道的一部分,并且我正在尝试在单个环境中实施整个管道以最大程度地减少麻烦。 我找到了一个与当前问题略有相关的已回答问题 here。然而,在那种情况下,集群的数量是事先已知的,而在我的情况下,集群的数量可以是从 1 到实例数等于 1 的任何值(前提是没有实例与另一个实例相邻)。

我可以为此目标编写一个函数,但这会很耗时,而且可能效率不高,因为除了寻找非零实例之外,我想不出任何其他策略,检查每个邻居实例,如果有的话这些是非零的,而不是检查它的邻居等等。

由于聚类步骤包含在嵌套交叉验证循环中,您可以亲眼看到我需要更高效的东西(或者可能只是用 C 编写的相同东西,以便更快)。

你们中有人知道可以帮助我的功能或软件包吗?

更新

为了回答评论,我的 "sparse" 数组在大多数元素为零的意义上是稀疏的,而不是在以稀疏格式保存的意义上。 这是一个玩具示例(这确实是我原始数组的非零元素周围的裁剪,具有暗淡的 (91,109,91))。

sparse_array = structure(c(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 
1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 
0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 
0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
0, 0, 0, 0, 0, 0, 0), .Dim = c(13L, 3L, 6L))

更新 2

我正在 Windows x64 机器上工作,使用 RStudio 1.0.153 和 R 版本 3.4.2(Short Summer)

更新 3

我已经尝试过@gdkrmr 给出的答案,虽然它对于给定的示例工作正常,但它无法推广到更大更复杂的图像。具体来说,它在我的图像中过度分离了集群,这意味着确实相互接触的体素有时会被分成不同的集群。 你可以自己想象一下下载这个image和运行下面的代码

读取 3d 图像

library(oro.nifti)
roi <- readNIfTI("image_to_cluster.nii")
roi_img <- cal_img(roi)

以数组形式读取数据

array_img <- roi@.Data

以稀疏格式转换

sparse_format <- (array_img > 0) %>%
  which(., arr.ind = TRUE)

找到相邻体素

neighborhoods <- sparse_format %>%
  dist %>%
  as.matrix %>%
  {. < 2}

分配集群标签

cluster <- 1:nrow(sparse_format)
for (i in 1:nrow(sparse_format)) {
  cl_idx <- cluster[i]
  cluster[neighborhoods[, i]] <- cl_idx
}
sparse_format <- sparse_format %>%
  as_data_frame(.) %>%
  mutate(cluster_id = cluster)

将簇写入新的 3d 图像

new_img <- roi
new_img@.Data <- array(0,c(74,92,78))

for (cl in cluster) {
  new_img@.Data[sparse_format %>% filter(., cluster_id == cl) %>% select(dim1,dim2,dim3) %>% as.matrix] <- cl
}
writeNIfTI(new_img, "test", verbose=TRUE)

现在,如果您打开文件 test.nii.gz(您可以使用 mricron 等),您会看到在大致坐标 37 23 15 处有一个大集群已被拆分在 3 个不同的集群中,即使所有体素都已连接。


您可以使用 spatstat 包来完成此操作。你需要新的 从 github 创建了分支 connected.pp3,如果你可以安装它 加载了 devtoolsremotes 包(这里我使用 remotes):

library(remotes)
install_github("spatstat/spatstat")

library(spatstat)

网格和边界框

grid <- expand.grid(0:4,0:4,0:4)
bb <- box3(range(grid[,1]), range(grid[,2]), range(grid[,3]))

稀疏数据数组(以及稀疏行的 id)

grid$id <- 1:nrow(grid)
set.seed(42)
a <- grid[sample(nrow(grid), 20),]
a
#>     Var1 Var2 Var3  id
#> 115    4    2    4 115
#> 117    1    3    4 117
#> 36     0    2    1  36
#> 102    1    0    4 102
#> 78     2    0    3  78
#> 63     2    2    2  63
#> 88     2    2    3  88
#> 16     0    3    0  16
#> 77     1    0    3  77
#> 82     1    1    3  82
#> 53     2    0    2  53
#> 116    0    3    4 116
#> 106    0    1    4 106
#> 29     3    0    1  29
#> 52     1    0    2  52
#> 104    3    0    4 104
#> 107    1    1    4 107
#> 13     2    2    0  13
#> 51     0    0    2  51
#> 60     4    1    2  60

转换为 3D 点模式并找到连接的组件(返回为 所谓的分数)。正如@gdkrmr 所指出的那样 距离小于 2 是邻居(这里我们使用 1.8,但任何 在 sqrt(3) 和 2 之间应该可以工作。

x <- pp3(a[,1], a[,2], a[,3], bb)
x_labelled <- connected.pp3(x, R = 1.8)
df <- data.frame(cluster_id = marks(x_labelled), point_id = a$id)

为了更好的打印,我们根据簇 id 排序

df[order(df$cluster_id, df$point_id),]
#>    cluster_id point_id
#> 1           1      115
#> 14          2       29
#> 19          2       51
#> 15          2       52
#> 11          2       53
#> 20          2       60
#> 6           2       63
#> 9           2       77
#> 5           2       78
#> 10          2       82
#> 7           2       88
#> 4           2      102
#> 16          2      104
#> 13          2      106
#> 17          2      107
#> 12          2      116
#> 2           2      117
#> 8           3       16
#> 3           3       36
#> 18          4       13

这是一个纯R的解决方案,它利用了相邻体素的最大距离是sqrt(d) < 2 if d <= 3:

library(rgl)
library(magrittr)
sparse_format <- (sparse_array > 0) %>%
  which(., arr.ind = TRUE)
neighborhoods <- sparse_format %>%
  dist %>%
  as.matrix %>%
  {. < 2}
n <- nrow(sparse_format)

perm <- 1:n
for (i in 1:n) {
  perm[i:n] <- perm[i:n][
    order(neighborhoods[perm[i], perm][i:n], 
          decreasing = TRUE)
  ]
}
neighborhoods <- neighborhoods[perm, perm]
sparse_format <- sparse_format[perm, ]

cluster <- 1:n
for (i in 1:n) {
  cl_idx <- cluster[i]
  cluster[neighborhoods[, i]] <- cl_idx
}
plot3d(sparse_format, col = cluster)

更新:添加了 neighborhoods 矩阵的排序以查找连接的集群。这变得非常慢(您的示例图像大约 30 秒),但我认为仍有很大的优化空间。如果您想要一个真正快速的解决方案,请查看 Julia language, especially at Images.jl.

更新:使第一个循环变快。