a b/R/radiomics_spatial.R
1
#' Calculate spatial radiomic features on a 2D or 3D array
2
#'
3
#' @param data Any 2D or 3D image (as matrix or array) to calculate spatial features
4
#' @param features = spatial radiomic features to calculate
5
#' @return Values from selected features
6
#' @importFrom abind abind
7
#' @export
8
radiomics_spatial <- function(data,
9
                              features = c('mi', 'gc', 'fd')){
10
11
12
  # Figure data dimension
13
  dimData <- length(dim(data))
14
15
  #2D
16
  if(dimData == 2){
17
    if('mi' %in% features){
18
      mi_value <- moran2D(data)
19
    }else(mi_value = NULL)
20
    if('gc' %in% features){
21
      gc_value <- geary2D(data)
22
    }else(gc_value = NULL)
23
    if('fd' %in% features){
24
      fd_value <- fd2D(data)
25
    }else(fd_value = NULL)
26
27
  }
28
29
  # 3D
30
  if(dimData == 3){
31
32
    if('mi' %in% features){
33
      mi_value <- moran3D(data)
34
    }else(mi_value = NULL)
35
    if('gc' %in% features){
36
      gc_value <- geary3D(data)
37
    }else(gc_value = NULL)
38
    if('fd' %in% features){
39
      fd_value <- NULL #fd3D(data) # still need to work on this
40
    }else(fd_value = NULL)
41
42
  }
43
44
  featuresList <- list(
45
    mi = mi_value,
46
    gc = gc_value,
47
    fd = fd_value
48
  )
49
  if(length(features)==1){
50
    featuresList = unlist(featuresList[features])
51
  }
52
53
  return(featuresList)
54
55
}
56
57
58
# Calculate Moran's I in 3D
59
moran3D <- function(mat){
60
61
  # Set up
62
  n <- length(mat[is.na(mat)==F])
63
  xbar <- mean(mat, na.rm=T)
64
  diff <- mat - xbar
65
  diff2 <- diff**2
66
  sum_diff2 <- sum(diff2, na.rm = T)
67
68
  # Dimension of matrix
69
  nx <- dim(diff)[1]
70
  ny <- dim(diff)[2]
71
  nz <- dim(diff)[3]
72
73
  # Rook shift
74
  left <- diff * abind(diff[-1,,], matrix(NA, nrow = ny, ncol = nz), along = 1)
75
  right <- diff * abind(matrix(NA, nrow = ny, ncol = nz), diff[-nx,,], along = 1)
76
  front <- diff * abind(diff[,-1,], matrix(NA, nrow = nx, ncol = nz), along = 2)
77
  back <- diff * abind(matrix(NA, nrow = nx, ncol = nz), diff[,-ny,], along = 2)
78
  up <- diff * abind(diff[,,-1], matrix(NA, nrow = nx, ncol = ny), along = 3)
79
  down <- diff * abind(matrix(NA, nrow = nx, ncol = ny), diff[,,-nz], along = 3)
80
81
  # Calcultate weights
82
  weights <- sum(length(left[is.na(left) == F])) +
83
    sum(length(right[is.na(right) == F])) +
84
    sum(length(front[is.na(front) == F])) +
85
    sum(length(back[is.na(back) == F])) +
86
    sum(length(up[is.na(up) == F])) +
87
    sum(length(down[is.na(down) == F]))
88
89
  # Change all "NAs" to zero, so we can efficiently sum matrices
90
  left[is.na(left)] <- 0
91
  right[is.na(right)] <- 0
92
  front[is.na(front)] <- 0
93
  back[is.na(back)] <- 0
94
  up[is.na(up)] <- 0
95
  down[is.na(down)] <- 0
96
97
  # Put info together to calculate MI
98
  sumv <- left + right + front + back + up + down
99
  local_mi <- sumv / sum_diff2 * n / weights
100
  global_mi <- sum(local_mi)
101
102
  return(global_mi)
103
}
104
105
106
107
108
# Calculate Moran's I in 2D
109
moran2D<-function(mat){
110
111
  # Set up
112
  n <- length(mat[is.na(mat)==F])
113
  xbar <- mean(mat, na.rm=T)
114
  diff <- mat - xbar
115
  diff2 <- diff**2
116
  sum_diff2 <- sum(diff2, na.rm = T)
117
118
  # Dimension of matrix
119
  nrow <- dim(diff)[1]
120
  ncol <- dim(diff)[2]
121
122
  # Rook shift
123
  up <- diff * rbind(diff[-1,],rep(NA,ncol))
124
  down <- diff * rbind(rep(NA,ncol),diff[-nrow,])
125
  left <- diff * cbind(diff[,-1],rep(NA,nrow))
126
  right <- diff * cbind(rep(NA,nrow),diff[,-ncol])
127
128
  # Calcultate weights
129
  weights <- sum(length(left[is.na(left) == F])) +
130
    sum(length(right[is.na(right) == F])) +
131
    sum(length(up[is.na(up) == F])) +
132
    sum(length(down[is.na(down) == F]))
133
134
  # Change all "NAs" to zero, so we can efficiently sum matrices
135
  left[is.na(left)] <- 0
136
  right[is.na(right)] <- 0
137
  up[is.na(up)] <- 0
138
  down[is.na(down)] <- 0
139
140
  # Put info together to calculate MI
141
  sumv <- left + right + up + down
142
  local_mi <- sumv / sum_diff2 * n / weights
143
  global_mi <- sum(local_mi)
144
145
  return(global_mi)
146
}
147
148
149
150
151
# Calculate Geary's C in 3D
152
geary3D<-function(mat){
153
154
  # Set up
155
  n <- length(mat[is.na(mat)==F])
156
  xbar <- mean(mat, na.rm=T)
157
  diff <- mat - xbar
158
  diff2 <- diff**2
159
  sum_diff2 <- sum(diff2, na.rm = T)
160
161
  # Dimension of matrix
162
  nx <- dim(diff)[1]
163
  ny <- dim(diff)[2]
164
  nz <- dim(diff)[3]
165
166
  # Rook shift
167
  left <- (mat - abind(mat[-1,,], matrix(NA, nrow = ny, ncol = nz), along = 1) )^2
168
  right <- (mat - abind(matrix(NA, nrow = ny, ncol = nz), mat[-nx,,], along = 1) )^2
169
  front <- (mat - abind(mat[,-1,], matrix(NA, nrow = nx, ncol = nz), along = 2) )^2
170
  back <- (mat - abind(matrix(NA, nrow = nx, ncol = nz), mat[,-ny,], along = 2) )^2
171
  up <- (mat - abind(mat[,,-1], matrix(NA, nrow = nx, ncol = ny), along = 3) )^2
172
  down <- (mat - abind(matrix(NA, nrow = nx, ncol = ny), mat[,,-nz], along = 3) )^2
173
174
  # Calcultate weights
175
  weights <- sum(length(left[is.na(left) == F])) +
176
    sum(length(right[is.na(right) == F])) +
177
    sum(length(front[is.na(front) == F])) +
178
    sum(length(back[is.na(back) == F])) +
179
    sum(length(up[is.na(up) == F])) +
180
    sum(length(down[is.na(down) == F]))
181
182
  # Change all "NAs" to zero, so we can efficiently sum matrices
183
  left[is.na(left)] <- 0
184
  right[is.na(right)] <- 0
185
  front[is.na(front)] <- 0
186
  back[is.na(back)] <- 0
187
  up[is.na(up)] <- 0
188
  down[is.na(down)] <- 0
189
190
  # Put info together to calculate GC
191
  sumv <- left + right + front + back + up + down
192
  local_gc <- sumv / sum_diff2 * (n - 1) / (2 * weights)
193
  global_gc <- sum(local_gc)
194
195
  return(global_gc)
196
}
197
198
199
# Calculate Geary's C in 2D
200
geary2D<-function(mat){
201
202
  # Set up
203
  n <- length(mat[is.na(mat)==F])
204
  xbar <- mean(mat, na.rm=T)
205
  diff <- mat - xbar
206
  diff2 <- diff**2
207
  sum_diff2 <- sum(diff2, na.rm = T)
208
209
  # Dimension of matrix
210
  nrow <- dim(diff)[1]
211
  ncol <- dim(diff)[2]
212
213
  # Rook shift
214
  up <- (mat - rbind(mat[-1,],rep(NA,ncol)) )^2
215
  down <- (mat - rbind(rep(NA,ncol),mat[-nrow,]) )^2
216
  left <- (mat - cbind(mat[,-1],rep(NA,nrow)) )^2
217
  right <- (mat - cbind(rep(NA,nrow),mat[,-ncol]) )^2
218
219
  # Calcultate weights
220
  weights <- sum(length(left[is.na(left) == F])) +
221
    sum(length(right[is.na(right) == F])) +
222
    sum(length(up[is.na(up) == F])) +
223
    sum(length(down[is.na(down) == F]))
224
225
  # Change all "NAs" to zero, so we can efficiently sum matrices
226
  left[is.na(left)] <- 0
227
  right[is.na(right)] <- 0
228
  up[is.na(up)] <- 0
229
  down[is.na(down)] <- 0
230
231
  # Put info together to calculate GC
232
  sumv <- left + right + up + down
233
  local_gc <- sumv / sum_diff2 * (n - 1) / (2 * weights)
234
  global_gc <- sum(local_gc)
235
236
  return(global_gc)
237
}
238
239
240
241
242
243
244
# Calculate fractal dimension in 2D
245
fd2D <- function(data) {
246
  fdx<-apply(data,1,fd1)
247
  fdy<-apply(data,2,fd1)
248
  fd<-c(fdx,fdy)
249
  fd_sub<-fd[fd<2]
250
  FD <- ifelse(length(fd_sub)<100,NA,1 + median(fd_sub,na.rm=T))
251
  return(FD)
252
}
253
fd1<-function(i){
254
  n <- length(i)
255
  g1 <- abs(i[2:n]-i[1:(n-1)])
256
  sumg1<-sum(g1,na.rm=T)/(2*length(g1[is.na(g1)==F]))
257
  g2 <- abs(i[3:n]-i[1:(n-2)])
258
  sumg2 <-  sum(g2,na.rm=T)/(2*length(g2[is.na(g2)==F]))
259
  slope <- sum(log(sumg2)-log(sumg1),na.rm=T)/log(2)
260
  fd <- 2-slope
261
  return(fd)
262
}
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278