“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)
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
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:
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
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 ... ") }
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 ... ") }
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.