Intro

Occasionally CATMAID users add the soma tag multiple times in error. We can find such neurons and generate CATMAID URLs to inspect them.

Setup

First load main packages

library(elmr)
cl=try(catmaid_login())
catmaid_available=inherits(cl, "catmaid_connection")
library(knitr)
# only run if catmaid available
# and cache so only run once per day
opts_chunk$set(eval=inherits(cl, "catmaid_connection"),cache.extra=Sys.Date())
library(dplyr)
rgl::setupKnitr()

Finding neurons with multiple somata

First let's fetch information about all the labels (aka tags) applied to nodes in the current project.

label_stats=catmaid_get_label_stats()

Now let's restrict to cases where there are multiple soma tags per skeleton

# select soma labels
soma_labels = label_stats %>%
  filter(labelName == 'soma') %>%
  group_by(skeletonID)

# select skeleton ids for neurons with multiple cell bodies
multiple_soma = soma_labels %>%
  count(skeletonID) %>%
  filter(n > 1) %>%
  arrange(desc(n))
  multiple_soma_info = soma_labels %>%
  filter(skeletonID %in% multiple_soma$skeletonID)

multiple_soma_info = soma_labels %>% 
  filter(skeletonID%in% multiple_soma$skeletonID)

XYZ position of the nodes we picked

# wrapper for tree node details
node_details <- function(tnid) {
  res=catmaid_get_treenodes_detail(tnid)
  ul=catmaid_get_user_list()
  res$login=ul$login[match(res$user_id, ul$id)]
  # we expect these to be capitalised elsewhere
  cn=colnames(res)
  cn[cn%in%c("x","y","z")]=toupper(cn[cn%in%c("x","y","z")])
  colnames(res) <- cn
  res[c("X","Y","Z","radius","login")]
}

# note that we need to transpose the results of vnode_xyz to get X,Y,Z columns
multiple_soma_info <- cbind(as.data.frame(multiple_soma_info),
                            node_details(multiple_soma_info$treenodeID))

Now let's calculate the distance from the neuropil surface

multiple_soma_info$d=pointsinside(xyzmatrix(multiple_soma_info),
                                  FAFB.surf, rval = 'distance')
multiple_soma_info %>% 
  arrange(skeletonID, d) %>% 
  group_by(skeletonID) %>% 
  mutate(rank=row_number()) -> multiple_soma_info
kable(multiple_soma_info)

This lets us see that in some cases there are two soma tags outside the neuropil (negative d) and close together - these are probably duplicates - whereas in other cases it is likely that points were added in error. We can also plot the points colouring them by their rank order (most external first).

# make a colour palette with as many entries as the maximum number of soma 
# tags in a neuron
pal=rainbow(max(multiple_soma_info$rank))
multiple_soma_info %>% 
  with(expr = spheres3d(X,Y,Z, col=pal[rank], rad=2000))
plot3d(FAFB)
par3d(zoom=.6)

Now we can use this information to construct an url for each node.

multiple_soma_info %>%
  rowwise() %>%
  mutate(url = open_fafb(
    cbind(X, Y, Z),
    active_skeleton_id = skeletonID,
    active_node_id = treenodeID,
    open = FALSE
  )) -> multiple_soma_info

It might be useful to know who 'owns' each neuron. I think the simplest way to assign this is by the user who has traced most nodes for each skeleton (since there may be different users responsible for each soma).

get_top_user <- function(x, ...) {
  ul=catmaid_get_user_list(...)
  # save time by checking unique skids only
  ddx=unique(x)
  gtu_one <- function(x, ...) {
    t <- try({
      res=catmaid_get_contributor_stats(x, ...)
      w=which.max(res$node_contributors$n)
      ul$login[match(res$node_contributors$id[w], ul$id)]
    })
    if(inherits(t, 'try-error')) NA_character_ else t
  }
  topus <- sapply(ddx, gtu_one)
  topus[match(x, ddx)]
}

Google sheet

Let's make a google sheet with all those urls that we can then review manually:

library(googlesheets)
# helper function to upload via temp file
# since writing cells via API is very slow
gs_upload_tf <- function(x, ...) {
  tf=tempfile(fileext = '.tsv')
  on.exit(unlink(tf))
  write.table(x, file=tf, sep="\t", row.names = FALSE)
  gs_upload(tf, ...)
}

multiple_soma_info %>% 
  arrange(skeletonID, d) %>% 
  group_by(skeletonID) %>%
  mutate(user=get_top_user(skeletonID)) %>%
  gs_upload_tf(sheet_title = 'multi_soma_neurons')

As an alternative we can divide that up with one worksheet per user.

library(googlesheets)
gs <- googlesheets::gs_new("multi_soma_neurons_by_user")
gs_add_sheet <- function(x, gs, ...) {
  gs_ws_new(row_extent = nrow(x)+1, col_extent = ncol(x), ss = gs, ..., input=x, col_names=T)
}
multiple_soma_info %>% 
  arrange(skeletonID, d) %>% 
  mutate(user=factor(get_top_user(skeletonID))) -> msi2

for(u in levels(msi2$user)) {
  gs_add_sheet(subset(msi2, user==u), gs, ws_title=u)
  cat(".")
}


jefferis/elmr documentation built on Sept. 9, 2023, 1:54 p.m.