diff --git a/CHANGELOG.md b/CHANGELOG.md index e33d1911..e2642bd6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ ## Changes since last CRAN release -* `27324b88 (HEAD -> master)` [_`dipterix`_]: Fixing the side panels so the background color is always black -* `ad4633fe (origin/master, origin/HEAD)` [_`dipterix`_]: Canvas and screenshot is transparent when background is `#ffffff` +* `dd4b0b0a (HEAD -> master, origin/master, origin/HEAD)` [_`dipterix`_]: Fixing the side panels so the background color is always black +* `ad4633fe` [_`dipterix`_]: Canvas and screenshot is transparent when background is `#ffffff` * `ff5eca46` [_`dipterix`_]: Fixed shader ignoring always-depth flag when outline is off; updated `jsPDF` to the 2.5.2 build * `ad202235` [_`dipterix`_]: Transition animation allows video recording * `94ec004f` [_`dipterix`_]: Fixed screenshot button and improved video quality diff --git a/DESCRIPTION b/DESCRIPTION index 81ae82e1..52ca7c3c 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: threeBrain Type: Package Title: Your Advanced 3D Brain Visualization -Version: 1.2.0.9009 +Version: 1.2.0.9010 Authors@R: c( person("Zhengjia", "Wang", email = "dipterix.wang@gmail.com", role = c("aut", "cre", "cph")), person("John", "Magnotti", email = "John.Magnotti@Pennmedicine.upenn.edu", role = c("ctb", "res")), diff --git a/NAMESPACE b/NAMESPACE index ba7772bd..ab8657f5 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -47,6 +47,7 @@ export(freeserfer_colormap) export(freesurfer_brain) export(freesurfer_brain2) export(freesurfer_lut) +export(generate_cortical_parcellation) export(generate_smooth_envelope) export(generate_subcortical_surface) export(geom_freemesh) diff --git a/R/class_brain.R b/R/class_brain.R index 5edefdae..10473859 100644 --- a/R/class_brain.R +++ b/R/class_brain.R @@ -271,12 +271,15 @@ Brain2 <- R6::R6Class( }, - add_annotation = function(annotation, surface_type = "pial") { + add_annotation = function(annotation, surface_type = "pial", template_subject = "fsaverage") { if(tolower(surface_type) == "pial.t1") { surface_type <- "pial" } if(!surface_type %in% self$surface_types) { - return(invisible()) + self$add_surface(surface_type) + if(!surface_type %in% self$surface_types) { + return(invisible()) + } } annot_dir <- dirname(annotation) annot_fname <- filename(annotation) @@ -294,6 +297,22 @@ Brain2 <- R6::R6Class( surface_instance <- self$surfaces[[surface_type]] + if(!length(lh_path) || !length(rh_path)) { + # annot file is missing; generate one on the fly + tryCatch({ + generate_cortical_parcellation(brain = self, + template_subject = template_subject, + annotation = annot_fname, + add_annotation = FALSE) + lh_path <- file.path(label_path, sprintf(c("lh.%s.annot", "lh.%s"), annot_fname)) + lh_path <- lh_path[file.exists(lh_path)] + rh_path <- file.path(label_path, sprintf(c("rh.%s.annot", "rh.%s"), annot_fname)) + rh_path <- rh_path[file.exists(rh_path)] + }, error = function(e) { + warning(e) + }) + } + if(length(lh_path)) { surface_instance$left_hemisphere$set_annotation(annotation, lh_path[[1]]) } diff --git a/R/class_brainatlas.R b/R/class_brainatlas.R index cb41d401..be88c910 100644 --- a/R/class_brainatlas.R +++ b/R/class_brainatlas.R @@ -248,3 +248,5 @@ create_voxel_cube <- function(mni_ras, value, colormap, } re } + + diff --git a/R/template_cortical-annotation.R b/R/template_cortical-annotation.R new file mode 100644 index 00000000..0cfca788 --- /dev/null +++ b/R/template_cortical-annotation.R @@ -0,0 +1,147 @@ +#' Generate cortical annotations from template using surface mapping +#' @description +#' This is a low-level function. Use \code{brain$add_annotation} instead. +#' +#' @param brain Brain object +#' @param template_subject template subject where the annotation is stored +#' @param annotation annotation name in the label folder; default is +#' \code{'Yeo2011_7Networks_N1000'}, standing for +#' \code{'lh.Yeo2011_7Networks_N1000.annot'} and +#' \code{'rh.Yeo2011_7Networks_N1000.annot'}. +#' @param add_annotation whether to add annotation to \code{brain} +#' @returns \code{brain} with the annotation added if \code{add_annotation} +#' is true +#' +#' @export +generate_cortical_parcellation <- function( + brain, template_subject = "fsaverage", annotation = "Yeo2011_7Networks_N1000", + add_annotation = TRUE) { + + # DIPSAUS DEBUG START + # devtools::load_all() + # brain <- raveio::rave_brain("demo/DemoSubject") + # template_subject <- "fsaverage" + # annotation <- "Yeo2011_17Networks_N1000" + + if(is.null(brain$surfaces$sphere.reg)) { + brain$add_surface("sphere.reg") + on.exit({ + brain$surfaces$sphere.reg <- NULL + }) + } + + if(!length(brain$surfaces$sphere.reg)) { + stop("The FreeSurfer brain object does not have `sphere.reg` surface. Did you finish `recon-all`?") + } + # make sure fsaverage is present + tempolate_path <- file.path(default_template_directory(), template_subject) + if(!dir.exists(tempolate_path)) { + message("Template `", template_subject, "` is missing. Downloading it from RAVE (this is one-time procedure).") + message(sprintf("Running `threeBrain::download_template_subject(subject_code = '%s')`", template_subject)) + download_template_subject(subject_code = template_subject) + } + + # check if the annoations are available + lh_annot_path <- file.path(tempolate_path, "label", sprintf("lh.%s.annot", annotation)) + rh_annot_path <- file.path(tempolate_path, "label", sprintf("rh.%s.annot", annotation)) + + if(!file.exists(lh_annot_path)) { + stop(sprintf("Template `%s` does not have FreeSurfer annotation `%s` for the left hemisphere (%s is missing).", template_subject, annotation, basename(lh_annot_path))) + } + if(!file.exists(rh_annot_path)) { + stop(sprintf("Template `%s` does not have FreeSurfer annotation `%s` for the right hemisphere (%s is missing).", template_subject, annotation, basename(rh_annot_path))) + } + + # sphere.reg for template + lh_sphere_reg_path_template <- file.path(tempolate_path, "surf", "lh.sphere.reg") + rh_sphere_reg_path_template <- file.path(tempolate_path, "surf", "rh.sphere.reg") + + # read sphere.reg for native + lh_sphere_reg_path <- file.path(brain$base_path, "surf", "lh.sphere.reg") + rh_sphere_reg_path <- file.path(brain$base_path, "surf", "rh.sphere.reg") + + # build mapping for each native node + + # left + sphere_reg <- freesurferformats::read.fs.surface(lh_sphere_reg_path) + sphere_reg_template <- freesurferformats::read.fs.surface(lh_sphere_reg_path_template) + annot_template <- freesurferformats::read.fs.annot(lh_annot_path) + + kdtree <- ravetools::vcg_kdtree_nearest( + target = sphere_reg_template$vertices[, 1:3, drop = FALSE], + query = sphere_reg$vertices[, 1:3, drop = FALSE], + k = 1 + ) + new_label_codes <- annot_template$label_codes[kdtree$index[, 1]] + + annot_path <- file.path(brain$base_path, "label", sprintf("lh.%s.annot", annotation)) + freesurferformats::write.fs.annot( + filepath = annot_path, num_vertices = as.integer(length(new_label_codes)), + colortable = annot_template$colortable_df, labels_as_colorcodes = new_label_codes) + + + # pial_annotated <- merge( + # ieegio::read_surface(file.path(brain$base_path, "surf", "lh.pial")), + # ieegio::read_surface(annot_path) + # ); plot(pial_annotated) + + # right + sphere_reg <- freesurferformats::read.fs.surface(rh_sphere_reg_path) + sphere_reg_template <- freesurferformats::read.fs.surface(rh_sphere_reg_path_template) + annot_template <- freesurferformats::read.fs.annot(rh_annot_path) + + kdtree <- ravetools::vcg_kdtree_nearest( + target = sphere_reg_template$vertices[, 1:3, drop = FALSE], + query = sphere_reg$vertices[, 1:3, drop = FALSE], + k = 1 + ) + new_label_codes <- annot_template$label_codes[kdtree$index[, 1]] + + annot_path <- file.path(brain$base_path, "label", sprintf("rh.%s.annot", annotation)) + freesurferformats::write.fs.annot( + filepath = annot_path, num_vertices = as.integer(length(new_label_codes)), + colortable = annot_template$colortable_df, labels_as_colorcodes = new_label_codes) + + # pial_annotated <- merge( + # ieegio::read_surface(file.path(brain$base_path, "surf", "rh.pial")), + # ieegio::read_surface(annot_path) + # ); plot(pial_annotated) + + + # # right + # rh_sphere_reg <- ieegio::read_surface(rh_sphere_reg_path) + # rh_sphere_reg_template <- ieegio::read_surface(rh_sphere_reg_path_template) + # rh_annot_template <- ieegio::read_surface(rh_annot_path) + # + # kdtree <- ravetools::vcg_kdtree_nearest( + # t(rh_sphere_reg_template$geometry$vertices[1:3, , drop = FALSE]), + # t(rh_sphere_reg$geometry$vertices[1:3, , drop = FALSE]), + # 1 + # ) + # mapping_index <- as.vector(kdtree$index) + # + # # mapping_index <- apply(rh_sphere_reg$geometry$vertices, 2, function(p) { + # # which.min(colSums((rh_sphere_reg_template$geometry$vertices - p)^2)) + # # }) + # annot_name <- names(rh_annot_template$annotations$data_table)[[1]] + # new_annot <- rh_annot_template$annotations$data_table[[1]][mapping_index] + # + # rh_annot <- rh_annot_template + # rh_annot$sparse_node_index <- structure(seq_along(new_annot), start_index = 1L) + # annot_table <- structure(list(as.integer(new_annot)), names = annot_name) + # rh_annot$annotations$data_table <- data.table::as.data.table(annot_table) + # + # pial_annotated <- merge( + # ieegio::read_surface(file.path(brain$base_path, "surf", "rh.pial")), + # rh_annot) + # plot(pial_annotated, method = "r3js") + + # # save + # ieegio::write_surface(x = rh_annot, con = file.path(brain$base_path, "label", sprintf("rh.%s.annot", annotation)), format = "freesurfer", type = "annotations") + + if( add_annotation ) { + brain$add_annotation(sprintf("label/%s", annotation)) + } + invisible(brain) + +} diff --git a/adhoc/cortical_mapping.R b/adhoc/cortical_mapping.R new file mode 100644 index 00000000..e06d6362 --- /dev/null +++ b/adhoc/cortical_mapping.R @@ -0,0 +1,126 @@ + + + +generate_cortical_parcellation <- function( + brain, template_subject = "fsaverage", annotation = "Yeo2011_7Networks_N1000") { + + # DIPSAUS DEBUG START + # devtools::load_all() + # brain <- raveio::rave_brain("demo/DemoSubject") + # template_subject <- "fsaverage" + # annotation <- "Yeo2011_17Networks_N1000" + + if(is.null(brain$surfaces$sphere.reg)) { + brain$add_surface("sphere.reg") + on.exit({ + brain$surfaces$sphere.reg <- NULL + }) + } + + if(!length(brain$surfaces$sphere.reg)) { + stop("The FreeSurfer brain object does not have `sphere.reg` surface. Did you finish `recon-all`?") + } + # make sure fsaverage is present + tempolate_path <- file.path(default_template_directory(), template_subject) + if(!dir.exists(tempolate_path)) { + message("Template `", template_subject, "` is missing. Downloading it from RAVE (this is one-time procedure).") + message(sprintf("Running `threeBrain::download_template_subject(subject_code = '%s')`", template_subject)) + download_template_subject(subject_code = template_subject) + } + + # check if the annoations are available + lh_annot_path <- file.path(tempolate_path, "label", sprintf("lh.%s.annot", annotation)) + rh_annot_path <- file.path(tempolate_path, "label", sprintf("rh.%s.annot", annotation)) + + if(!file.exists(lh_annot_path)) { + stop(sprintf("Template `%s` does not have FreeSurfer annotation `%s` for the left hemisphere (%s is missing).", template_subject, annotation, basename(lh_annot_path))) + } + if(!file.exists(rh_annot_path)) { + stop(sprintf("Template `%s` does not have FreeSurfer annotation `%s` for the right hemisphere (%s is missing).", template_subject, annotation, basename(rh_annot_path))) + } + + # sphere.reg for template + lh_sphere_reg_path_template <- file.path(tempolate_path, "surf", "lh.sphere.reg") + rh_sphere_reg_path_template <- file.path(tempolate_path, "surf", "rh.sphere.reg") + + # read sphere.reg for native + lh_sphere_reg_path <- file.path(brain$base_path, "surf", "lh.sphere.reg") + rh_sphere_reg_path <- file.path(brain$base_path, "surf", "rh.sphere.reg") + + # build mapping for each native node + + # left + sphere_reg <- freesurferformats::read.fs.surface(lh_sphere_reg_path) + sphere_reg_template <- freesurferformats::read.fs.surface(lh_sphere_reg_path_template) + annot_template <- freesurferformats::read.fs.annot(lh_annot_path) + + kdtree <- ravetools::vcg_kdtree_nearest( + target = sphere_reg_template$vertices[, 1:3, drop = FALSE], + query = sphere_reg$vertices[, 1:3, drop = FALSE], + k = 1 + ) + new_label_codes <- annot_template$label_codes[kdtree$index[, 1]] + + annot_path <- file.path(brain$base_path, "label", sprintf("lh.%s.annot", annotation)) + freesurferformats::write.fs.annot( + filepath = annot_path, num_vertices = as.integer(length(new_label_codes)), + colortable = annot_template$colortable_df, labels_as_colorcodes = new_label_codes) + + + # pial_annotated <- merge( + # ieegio::read_surface(file.path(brain$base_path, "surf", "lh.pial")), + # ieegio::read_surface(annot_path) + # ); plot(pial_annotated) + + # right + sphere_reg <- freesurferformats::read.fs.surface(rh_sphere_reg_path) + sphere_reg_template <- freesurferformats::read.fs.surface(rh_sphere_reg_path_template) + annot_template <- freesurferformats::read.fs.annot(rh_annot_path) + + kdtree <- ravetools::vcg_kdtree_nearest( + target = sphere_reg_template$vertices[, 1:3, drop = FALSE], + query = sphere_reg$vertices[, 1:3, drop = FALSE], + k = 1 + ) + new_label_codes <- annot_template$label_codes[kdtree$index[, 1]] + + annot_path <- file.path(brain$base_path, "label", sprintf("rh.%s.annot", annotation)) + freesurferformats::write.fs.annot( + filepath = annot_path, num_vertices = as.integer(length(new_label_codes)), + colortable = annot_template$colortable_df, labels_as_colorcodes = new_label_codes) + + # pial_annotated <- merge( + # ieegio::read_surface(file.path(brain$base_path, "surf", "rh.pial")), + # ieegio::read_surface(annot_path) + # ); plot(pial_annotated) + +# +# # right +# rh_sphere_reg <- ieegio::read_surface(rh_sphere_reg_path) +# rh_sphere_reg_template <- ieegio::read_surface(rh_sphere_reg_path_template) +# rh_annot_template <- ieegio::read_surface(rh_annot_path) +# +# kdtree <- ravetools::vcg_kdtree_nearest( +# t(rh_sphere_reg_template$geometry$vertices[1:3, , drop = FALSE]), +# t(rh_sphere_reg$geometry$vertices[1:3, , drop = FALSE]), +# 1 +# ) +# mapping_index <- as.vector(kdtree$index) +# annot_name <- names(rh_annot_template$annotations$data_table)[[1]] +# new_annot <- rh_annot_template$annotations$data_table[[1]][mapping_index] +# +# rh_annot <- rh_annot_template +# rh_annot$sparse_node_index <- structure(seq_along(new_annot), start_index = 1L) +# annot_table <- structure(list(as.integer(new_annot)), names = annot_name) +# rh_annot$annotations$data_table <- data.table::as.data.table(annot_table) +# +# # pial_annotated <- merge(ieegio::read_surface(file.path(brain$base_path, "surf", "rh.pial")), rh_annot) +# # plot(pial_annotated, method = "r3js") +# +# # save +# ieegio::write_surface(x = rh_annot, con = file.path(brain$base_path, "label", sprintf("rh.%s.annot", annotation)), format = "freesurfer", type = "annotations") + + brain$add_annotation(sprintf("label/%s", annotation)) + invisible(annotation) + +} diff --git a/man/generate_cortical_parcellation.Rd b/man/generate_cortical_parcellation.Rd new file mode 100644 index 00000000..0522dc28 --- /dev/null +++ b/man/generate_cortical_parcellation.Rd @@ -0,0 +1,32 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/template_cortical-annotation.R +\name{generate_cortical_parcellation} +\alias{generate_cortical_parcellation} +\title{Generate cortical annotations from template using surface mapping} +\usage{ +generate_cortical_parcellation( + brain, + template_subject = "fsaverage", + annotation = "Yeo2011_7Networks_N1000", + add_annotation = TRUE +) +} +\arguments{ +\item{brain}{Brain object} + +\item{template_subject}{template subject where the annotation is stored} + +\item{annotation}{annotation name in the label folder; default is +\code{'Yeo2011_7Networks_N1000'}, standing for +\code{'lh.Yeo2011_7Networks_N1000.annot'} and +\code{'rh.Yeo2011_7Networks_N1000.annot'}.} + +\item{add_annotation}{whether to add annotation to \code{brain}} +} +\value{ +\code{brain} with the annotation added if \code{add_annotation} +is true +} +\description{ +This is a low-level function. Use \code{brain$add_annotation} instead. +}