knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>"
)

"INSECTS possess a nervous system that is incredibly complex and differentiated, and whose sophistication attains ultramicroscopic levels ... Certainly, the grey substance [of the brain of vertebrates] has increased considerably in mass, but when one compares its structure with that of the brain of Apidae or Libellulidae, it looks as excessively coarse and rudimentary. It is like pretending to match the rough merit of a standing wall clock with that of a pocket watch, a marvel of delicacy and precision. As usually, the genius of life shines more in the construction of smaller than larger master pieces." (SR y Cajal)

Inspecting insect brains

It's not often that you get to see brains from a range of different beautiful insects. They look quite funky. I wonder how the size of their sub-domains change with the size of the brain itself? We can use neuromorphr alongside its dependency, nat to pull and plot these brains, which are hosted and curated by insectbraindb.org

Find brains

First, we need to find the brains hosted on insectbraindb.org. These are required, because we need at least one of these two bits of information in order to read neurons from the repository.

To find these items, let us search the repository for neurons from the two groups of animal in which we are interested:

# Load package
library(neuromorphr)
library(nat)

# Righto, which species does the database host?
species_info = insectbraindb_species_info()
insect.species = unique(species_info$scientific_name)

# Okay, so a bunch of bees, wasps, moths, beetles and 'worms'
print(unique(species_info$common_name))

Read brains

Armed with this information, we can now read the the brains as nat package hxsurf objects. You can find a detailed explanation of this data format, and its basic manipulation with nat tools, here. Importantly, we can sub-divide a hxsurf brain by its constituent regions, in this case, neuropils, for visualisation and analysis.

# Let's try to read every brain the repository has
insect.brains = list()
for(insect in insect.species){
  message("Pulling ", insect, " brain mesh")
  for(sex in c("UNKNOWN", "MALE", "FEMALE")){
    message(sex)
    insect.brain = insectbraindb_read_brain(species = insect, brain.sex  = sex, progress = TRUE)
    insect.sex = paste(insect, sex, sep = " ")
    if(!is.null(insect.brain)){
      insect.brains[[insect.sex]] = insect.brain
    }
  }
}
# You will notice that we failed to read two brains there. It seems there is no reconstruction yet for Bombus terrestris, and that the Helicoverpa armigera requires a user with an account on insectbraindb.org 

# Because you donwload .obj files to the same temporary directory, which persists as long as the R session, re-reading the same brain again is quicker - because you have already downloaded the underlying files! Try re-running the above code in your R console to see (Markdown behaviour can be different).

# Let's also chuck in a Drosophila melanogaster brain for good measure 
if(!require('devtools')) install.packages("devtools")
if(!require('nat.flybrains')) devtools::install_github("jefferislab/nat.flybrains")
JFRC2NP.surf = nat.flybrains::JFRC2NP.surf
JFRC2NP.surf$scientific_name = "Drosophila melanogaster"
JFRC2NP.surf$common_name = "Vinegar fly" # Not fruit fly
JFRC2NP.surf$sex = "UNKNOWN" # Actually, it is intersex
insect.brains[["Drosophila melanogaster UNKNOWN"]] = JFRC2NP.surf
# It is the best of the insects after all

Plot brains

Now we have a fair few brains. They can't be plotted together really, at least not as 3D objects. But we can write some code to scan through them and have a quick looksee

# Hold right click to pan
nat::nopen3d(userMatrix = structure(c(0.999986588954926, -0.00360279157757759, 
-0.00371213257312775, 0, -0.00464127957820892, -0.941770493984222, 
-0.336223870515823, 0, -0.00228461623191833, 0.336236596107483, 
-0.941774606704712, 0, 0, 0, 0, 1), .Dim = c(4L, 4L)), zoom = 0.600000023841858, 
    windowRect = c(1460L, 65L, 3229L, 1083L))
for(ib in insect.brains){
  clear3d()
  message(ib$scientific_name, " the ", ib$common_name)
  plot3d(ib)
  progress = readline(prompt = "Press any key for next brain ... ")
}

Plot antennal lobes

We can also subset these brains by a particular brain area. Everyone likes the antennal lobe, maybe it's the best studied bit of insect neuro-anatomy. So let's have a gander at that.

# Let's have a look at how standardised the neuropil names are, across these species
neuropils = lapply(insect.brains, function(brain) sort(brain$neuropil_full_names))
als = sapply(neuropils, function(np) "Antennal Lobe"%in%np)
als
# Okay, so we have it in all, and indeed it has a capital A and a capital L

# Brains that contain a antennal lobe neuropil object
with.al = sapply(insect.brains, function(ib) sum(grepl("^Antennal Lobe$", ib$neuropil_full_names))>0)
insect.brains.with.al = insect.brains[with.al]
insect.brains.with.al[["Drosophila melanogaster"]] = JFRC2NP.surf # Got dropped out, as it does not have all the entries of a insectbraindb read brain

# Hold right click to pan
nat::nopen3d(userMatrix = structure(c(0.999986588954926, -0.00360279157757759, 
-0.00371213257312775, 0, -0.00464127957820892, -0.941770493984222, 
-0.336223870515823, 0, -0.00228461623191833, 0.336236596107483, 
-0.941774606704712, 0, 0, 0, 0, 1), .Dim = c(4L, 4L)), zoom = 0.600000023841858, 
    windowRect = c(1460L, 65L, 3229L, 1083L))
for(ib in insect.brains.with.al){
  clear3d()
  message(ib$scientific_name, " the ", ib$common_name)
  plot3d(subset(ib, "^AL_left|^AL_right|^AL_noside|^AL_R$|^AL_L$"), col = "red")
  #plot3d(ib, col = "lightgrey", alpha = 0.3)
  progress = readline(prompt = "Press any key for next brain ... ")
}

Comparing neuropils

Fab. Now what we really might like to know if how the volume of the antennal lobe differs across species. So let's put our analysis hat on and take a look at that.

# There are a few ways of calculating a volume for a mesh. I am going to be lazy and use the package alphashape3d.
# We will also use pbapply, because volume calculation can take some time, and it is nice to know how things are going
if(!require('alphashape3d')) install.packages("alphashape3d")
if(!require('pbapply')) install.packages("pbapply")

# Create function to calculate volume
calculate_volume <- function(brain, neuropil = NULL, alpha = 30){
  if(is.null(neuropil)){
    points = unique(nat::xyzmatrix(brain))
  }else{
    points = unique(nat::xyzmatrix(subset(brain, neuropil)))
  }
  a = ashape3d(points, alpha = alpha, pert = TRUE)
  volume_ashape3d(a)
}

# Get the volumes for the whole brain
insect.brain.volumes = pbapply::pbsapply(insect.brains.with.al, calculate_volume, neuropil = NULL)

# Get the volumes for just the antennal lobe
insect.al.volumes = pbapply::pbsapply(insect.brains.with.al, calculate_volume, neuropil = "^AL_left|^AL_right|^AL_noside|^AL_R$|^AL_L$")

# Hmm, that took a while

We can then use the wonderful ggplot2 package to visualise this data

# Assemble data.frame
species.with.al = sapply(insect.brains.with.al, function(x) x$common_name)
sex.with.al = unlist(sapply(insect.brains.with.al, function(x) x$sex[[1]]))
df = data.frame(species = c(species.with.al, species.with.al),
                sex = c(sex.with.al, sex.with.al),
                volume = c(insect.brain.volumes, insect.al.volumes),
                neuropil = c(rep("whole",length(insect.brain.volumes)),rep("AL",length(insect.al.volumes)))
)

# Plot!
if(!require('ggplot2')) install.packages("ggplot2")
ggplot2::ggplot(df, aes(x=species, y=volume, color = neuropil, group = neuropil, shape=sex)) +
  geom_jitter(position=position_dodge(0.2))+
  theme_classic()

# Hmm, let's see total brain volume against antennal lobe volume
df2 = data.frame(species = species.with.al,
                sex = sex.with.al,
                volume = insect.brain.volumes,
                al.volume = insect.al.volumes
)
ggplot2::ggplot(df2, aes(x=volume, y=al.volume, color = species)) +
  geom_point() + 
  geom_smooth(data = df2, aes(x=volume, y=al.volume, color = "black"), method=lm, se = FALSE)+
  theme_classic()

Also note, that some of these brains, such as that for Agrotis segetum, are not actually complete seemingly.

It is hard to interpret this sort of result. Perhaps we are better served by trying to see an olfactory - vision trade-off, and compare the AL size with the size of the optic lobes? The optic lobes are a bit more complicated, the consist of:

So a bit more complicated. But to be fair, the antennal lobe has plenty of glomeruli (~50 in Drosophila, >500 in ants) and those are not drawn out in these brain models.

# Which insects have which major optic neuropils?
lamina = sapply(neuropils, function(np) sum(grepl("Lamina", np)))
lamina
lobula = sapply(neuropils, function(np) sum(grepl("Lobula", np)))
lobula
medulla = sapply(neuropils, function(np) sum(grepl("Medulla", np)))
medulla
# Looks like the lamina is missing from many of these brains. We'll combine lobula and medulla volumes for our calculation

# Brains that contain a optic lobe neuropil objects, though not necessarily all of them
with.optic = sapply(insect.brains, function(ib) sum(grepl("^Lobula|^Medulla", ib$neuropil_full_names))>0)
insect.brains.with.optic = insect.brains[with.optic]

# Calculate the neuropil volumes
insect.optic.volumes = pbapply::pbsapply(insect.brains.with.optic, function(x)
  calculate_volume(subset(x, x$RegionList[grepl("^Lobula|^Medulla",x$neuropil_full_names)]))
)

# And out friend, Drosophila melanogaster
insect.brains.with.optic[["Drosophila melanogaster"]] = JFRC2NP.surf # Got dropped out, as it does not have all the entries of a insectbraindb read brain
insect.optic.volumes[["Drosophila melanogaster"]] = calculate_volume(subset(JFRC2NP.surf, "LOP_|ME_|LO_"), alpha = 3)

And let's try plotting again with Ggplot2

# Assemble data.frame
species.with.optic = sapply(insect.brains.with.optic, function(x) x$common_name)
sex.with.optic = unlist(sapply(insect.brains.with.optic, function(x) x$sex[[1]]))
df3 = data.frame(species = species.with.optic,
                al.volume = insect.al.volumes[names(insect.optic.volumes)],
                optic.volume = insect.optic.volumes
                )

# Plot!
ggplot2::ggplot(df3, aes(x=optic.volume, y=al.volume, color = species)) +
  geom_point() + 
  geom_smooth(data = df3, aes(x=optic.volume, y=al.volume), color  ="black", method=lm, se = FALSE)+
  theme_classic()

# Maybe we should normalise by total brain volume?
df4 = data.frame(species = species.with.optic,
                al.norm.volume = insect.al.volumes[names(insect.optic.volumes)]/insect.brain.volumes[names(insect.optic.volumes)],
                optic.norm.volume = insect.optic.volumes/insect.brain.volumes[names(insect.optic.volumes)]
                )
ggplot2::ggplot(df4, aes(x=optic.norm.volume, y=al.norm.volume, color = species)) +
  geom_point() + 
  geom_smooth(data = df4, aes(x=optic.norm.volume, y=al.norm.volume), color = "black", method=lm, se = FALSE)+
  theme_classic()

Those budworms really skew things!

Of course, to be sure, we would need to control by reconstruction method and work harder to establish the exact cell type correspondences.



natverse/insectbrainr documentation built on April 24, 2023, 6:06 a.m.