我需要使用邻域策略在3d数组中执行空间聚类。为了更清楚:我有一个3d图像,表示为稀疏的3d数组。有些实例是1,而大多数实例是0.我想将等于1的实例聚集在一起,彼此相邻(即如果我们将每个实例想象成一个多维数据集,我想将实例分组在一起面,边或角等于1)。
我需要在R中执行此操作,因为此步骤是用于机器学习的更长管道的一部分,并且我尝试在单个环境中实现整个管道以最小化头痛。 我找到了与现有问题略有关联的已回答问题here。但是,在这种情况下,簇的数量是事先已知的,而在我的情况下,簇的数量可以是从1到等于1的实例的数量(假设没有实例与另一个实例相邻)。
我可以为这个目标编写一个函数,但它会耗费时间并且可能效率不高,因为我无法想到除了寻找非零实例之外的任何其他策略,检查每个邻居实例,如果其中任何一个是非零,比检查其邻居等。
由于聚类步骤包含在嵌套交叉验证循环中,您可以亲眼看到我需要更高效的东西(或者可能只是用C编写的相同内容,以便更快)。
您是否知道任何可以帮助我的功能或包裹?
更新
要回答评论,我的“稀疏”数组在大多数元素为零的意义上是稀疏的,而不是以稀疏格式保存的意义。 这里有一个玩具示例(实际上是我原始数组的非零元素周围的作物,有暗淡(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(夏季短暂)
更新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个不同的群集,即使所有体素都已连接。
答案 0 :(得分:1)
您可以使用spatstat
包来执行此操作。你需要新的
从github创建分支connected.pp3
,如果你可以安装
加载devtools
或remotes
包(我在这里使用
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
答案 1 :(得分:0)
这是一个纯粹的R解决方案,如果sqrt(d) < 2
:
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语言,尤其是Images.jl。
更新:快速完成第一个循环。