|
4 | 4 | from the Brain Connectivity Toolbox (https://sites.google.com/site/bctnet/). |
5 | 5 | """ |
6 | 6 |
|
| 7 | +import itertools |
7 | 8 | import numpy as np |
8 | 9 | from scipy.linalg import expm |
9 | 10 | from scipy.stats import ttest_ind |
@@ -184,3 +185,144 @@ def rich_feeder_peripheral(x, sc, stat='median'): |
184 | 185 | equal_var=False, alternative='greater') |
185 | 186 |
|
186 | 187 | return rfp, pvals |
| 188 | + |
| 189 | + |
| 190 | +def navigation_wu(nav_dist_mat, sc_mat): |
| 191 | + """ |
| 192 | + Computes network navigation |
| 193 | +
|
| 194 | + Parameters |
| 195 | + ---------- |
| 196 | + nav_dist_mat : (N, N) array_like |
| 197 | + Connection length/distance matrix. |
| 198 | + sc_mat : (N, N) array_like |
| 199 | + Structural connectivity matrix, only used to get connectedness. |
| 200 | +
|
| 201 | + Returns |
| 202 | + ------- |
| 203 | + nav_sr : float |
| 204 | + Overall navigation success rate |
| 205 | + nav_sr_node : list of float |
| 206 | + Nodal navigation success rate |
| 207 | + nav_path_len : (N, N) array_like |
| 208 | + Navigation path length matrix, infinite if no path exists. |
| 209 | + nav_path_hop : (N, N) array_like |
| 210 | + Navigation path hop matrix, infinite if no path exists. |
| 211 | + nav_paths : list |
| 212 | + List of tuples containing source node, target node, path length, |
| 213 | + path hops, and the full path. |
| 214 | +
|
| 215 | + References |
| 216 | + ---------- |
| 217 | + Seguin, C., Van Den Heuvel, M. P., & Zalesky, A. (2018). Navigation |
| 218 | + of brain networks. Proceedings of the National Academy of Sciences, |
| 219 | + 115(24), 6297-6302. |
| 220 | +
|
| 221 | + Notes |
| 222 | + ----- |
| 223 | + Euclidean distance between nodes are usually used for `nav_dist_mat`. |
| 224 | + Distances returned from this function are also calculated from |
| 225 | + `nav_dist_mat`. |
| 226 | + Use :meth:`netneurotools.metrics.get_navigation_path_length` |
| 227 | + to get path length in other metrics. |
| 228 | +
|
| 229 | + See Also |
| 230 | + -------- |
| 231 | + netneurotools.metrics.get_navigation_path_length |
| 232 | + """ |
| 233 | + |
| 234 | + nav_paths = [] # (source, target, distance, hops, path) |
| 235 | + # navigate to the node that is closest to target |
| 236 | + for src in range(len(nav_dist_mat)): |
| 237 | + for tar in range(len(nav_dist_mat)): |
| 238 | + curr_pos = src |
| 239 | + curr_path = [src] |
| 240 | + curr_dist = 0 |
| 241 | + while curr_pos != tar: |
| 242 | + neig = np.where(sc_mat[curr_pos, :] != 0)[0] |
| 243 | + if len(neig) == 0: # not connected |
| 244 | + curr_path = [] |
| 245 | + curr_dist = np.inf |
| 246 | + break |
| 247 | + neig_dist_to_tar = nav_dist_mat[neig, tar] |
| 248 | + min_dist_idx = np.argmin(neig_dist_to_tar) |
| 249 | + |
| 250 | + new_pos = neig[min_dist_idx] |
| 251 | + # Assume it is connected, and only testing for loops. |
| 252 | + # if isempty(next_node) |
| 253 | + # || next_node == last_node |
| 254 | + # || pl_bin > max_hops |
| 255 | + if (new_pos in curr_path): |
| 256 | + curr_path = [] |
| 257 | + curr_dist = np.inf |
| 258 | + break |
| 259 | + else: |
| 260 | + curr_path.append(new_pos) |
| 261 | + curr_dist += nav_dist_mat[curr_pos, new_pos] |
| 262 | + curr_pos = new_pos |
| 263 | + nav_paths.append( |
| 264 | + (src, tar, curr_dist, len(curr_path) - 1, curr_path)) |
| 265 | + |
| 266 | + nav_sr = len([_ for _ in nav_paths if _[3] != -1]) / len(nav_paths) |
| 267 | + |
| 268 | + nav_sr_node = [] |
| 269 | + for k, g in itertools.groupby( |
| 270 | + sorted(nav_paths, key=lambda x: x[0]), key=lambda x: x[0] |
| 271 | + ): |
| 272 | + curr_path = list(g) |
| 273 | + nav_sr_node.append( |
| 274 | + len([_ for _ in curr_path if _[3] != -1]) / len(curr_path)) |
| 275 | + |
| 276 | + nav_path_len = np.zeros_like(nav_dist_mat) |
| 277 | + nav_path_hop = np.zeros_like(nav_dist_mat) |
| 278 | + for nav_item in nav_paths: |
| 279 | + i, j, length, hop, _ = nav_item |
| 280 | + if hop != -1: |
| 281 | + nav_path_len[i, j] = length |
| 282 | + nav_path_hop[i, j] = hop |
| 283 | + else: |
| 284 | + nav_path_len[i, j] = np.inf |
| 285 | + nav_path_hop[i, j] = np.inf |
| 286 | + |
| 287 | + return nav_sr, nav_sr_node, nav_path_len, nav_path_hop, nav_paths |
| 288 | + |
| 289 | + |
| 290 | +def get_navigation_path_length(nav_paths, alt_dist_mat): |
| 291 | + """ |
| 292 | + Get navigation path length from navigation results |
| 293 | +
|
| 294 | + Parameters |
| 295 | + ---------- |
| 296 | + nav_paths : list |
| 297 | + Return from netneurotools.metrics.navigation_wu |
| 298 | + alt_dist_mat : (N, N) array_like |
| 299 | + Alternative distance matrix, e.g. geodesic distance. |
| 300 | +
|
| 301 | + Returns |
| 302 | + ------- |
| 303 | + nav_path_len : (N, N) array_like |
| 304 | + Navigation path length matrix, in the alternative distance metric. |
| 305 | +
|
| 306 | + Notes |
| 307 | + ----- |
| 308 | + Following the original BCT function. |
| 309 | + `pl_wei = get_navigation_path_length(nav_paths, L)` |
| 310 | + L is strength-to-length remapping of the connection weight matrix. |
| 311 | + `pl_dis = get_navigation_path_length(nav_paths, D)` |
| 312 | + D is Euclidean distance between node centroids. |
| 313 | +
|
| 314 | + See Also |
| 315 | + -------- |
| 316 | + netneurotools.metrics.navigation_wu |
| 317 | + """ |
| 318 | + |
| 319 | + nav_path_len = np.zeros_like(alt_dist_mat) |
| 320 | + for nav_item in nav_paths: |
| 321 | + i, j, _, hop, path = nav_item |
| 322 | + if hop != -1: |
| 323 | + nav_path_len[i, j] = np.sum( |
| 324 | + [alt_dist_mat[path[_], path[_ + 1]] for _ in range(hop)] |
| 325 | + ) |
| 326 | + else: |
| 327 | + nav_path_len[i, j] = np.inf |
| 328 | + return nav_path_len |
0 commit comments