diff --git a/DESCRIPTION b/DESCRIPTION index e3cd8ea6..be6463f0 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: threeBrain Type: Package Title: 3D Brain Visualization -Version: 1.0.1.9108 +Version: 1.0.1.9109 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("aut", "res")), diff --git a/NEWS.md b/NEWS.md index 5bf8453a..235d166f 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,46 @@ +threeBrain 1.0.2 +======= + +* Removed a `shader` loop that accidentally used dynamic variable for looping, which may crash on `Windows` in certain situations +* Updated `README.md` +* Composer effects are removed +* Added default color to electrodes (JavaScript) to fix the localization electrode color not set correctly issues +* Added `z-index` base to the side canvas layer (`div`) +* Removed additional unused parameters from material call +* Used new material type to make brain more realistic +* New background color is implemented +* `Trackball` uses width instead of height of the viewer as the `Arcball` radius; mouse positions is calculated whenever mouse-down event is triggered (allowing more accurate track-ball calculation in `Shiny` applications) +* Made `col2hexstr` internal function +* Added a new electrode visibility mode, allowing to show contacts with no values but passing the threshold +* Added `brain$electrodes$fix_electrode_color` to fix the electrode colors under given data names (to display `DBS` electrodes, for example) +* Made `controller.load` more robust against errors +* Fixed the depth issue in `TextSprite` +* Some default controller values have changed to make more sense +* Outline render effect is removed; electrode outlines are implemented directly in the `shader` +* Updated `three.js` to `v160` with light model improved +* Added `rave_slices.nii` to allowed `MRI` prefix in `FreeSurfer` folder with highest priority, such that this image will be treated as default volume to load in side canvas (default is still `brain.finalsurfs.mgz`) +* Allow `MRI` to change brightness dynamically in viewer +* Ensure that `voxels` index from zeros +* Added `brain$get_atlas_labels` to guess the atlas labels from given masks or atlas files +* Allowed to spatially transform electrodes to desired coordinate system +* Updated citation +* The standalone viewer does not require static server anymore: everything is self-contained (require `pandoc`, which comes with `RStudio`) +* Changed mechanism on animation color map generator (so the color is more accurate for discrete variables) +* Added `png` to dependence +* Added support for `AC-PC` alignment, available in `RAVE` - `YAEL` module +* Allow `brain` to plot with additional customized geometries +* Added `plot_slides` to plot `MRI` centered at each electrode contact for slide-to-slide visualization +* Added Line-of-Sight view mode for side canvas; can be enabled using shortcut `m` (previous shortcut to change the material type is changed to `shift+M`) +* Fixed `CT` in `JavaScript` when the `sform` and `qform` are different (have different code) +* Allowed to specify the spacing for interpolation and extrapolation for unequally spaced electrodes +* Added electrode outlines +* `YAEL` paper is finally out +* Fixed incorrect sub-cortical labels +* Deprecated old format (no cache is needed anymore) +* Made electrodes opaque on main but transparent in side canvas +* Let controllers check if the variable is valid before set to avoid invalid viewer state during initialization +* Added `get_ijk2ras` to get `Norig` and `Torig` matrix + threeBrain 1.0.1 ======= diff --git a/R/class_brain.R b/R/class_brain.R index c1f732fc..4b5ff241 100644 --- a/R/class_brain.R +++ b/R/class_brain.R @@ -893,7 +893,8 @@ Brain2 <- R6::R6Class( plot_electrodes_on_slices = function( electrodes_to_plot = "all", volume = NULL, elec_table = NULL, zoom = 1, adjust_brightness = NA, - electrode_color = "green", electrode_size = 2, ..., + electrode_color = "green", electrode_size = 2, + verbose = TRUE, ..., decoration = function(i, j) { graphics::points(0, 0, pch = 20, col = electrode_color, cex = electrode_size) @@ -981,7 +982,7 @@ Brain2 <- R6::R6Class( } progress <- dipsaus::progress2("Plotting slices", max = length(plot_idx) + 1, - shiny_auto_close = TRUE) + shiny_auto_close = TRUE, quiet = !verbose) for(ii in plot_idx) { progress$inc(detail = sprintf("Generating graphs for electrode %s", dipsaus::deparse_svec(ii))) plot_slices( diff --git a/R/plot_volume-slices.R b/R/plot_volume-slices.R index c78b293d..3f32210b 100644 --- a/R/plot_volume-slices.R +++ b/R/plot_volume-slices.R @@ -18,6 +18,9 @@ #' @param zlim image plot value range, default is identical to \code{normalize} #' @param main image titles #' @param title_position title position; choices are \code{"left"} or \code{"top"} +#' @param which which plane to plot; default is \code{NULL}, which will trigger +#' new plots and add titles; set to \code{1} for 'Axial' plane, \code{2} for +#' 'Sagittal', and \code{3} for 'Coronal'. #' @param ... additional arguments passing into \code{\link[graphics]{image}} #' @returns Nothing #' @export @@ -25,7 +28,7 @@ plot_slices <- function( volume, transform = NULL, positions = NULL, zoom = 1, pixel_width = 0.5, col = c("black", "white"), normalize = NULL, zclip = NULL, zlim = normalize, main = "", title_position = c("left", "top"), - fun = NULL, nc = NA, ...) { + fun = NULL, nc = NA, which = NULL, ...) { # DIPSAUS DEBUG START # volume <- "~/rave_data/raw_dir/YAB/rave-imaging/fs/mri/brain.finalsurfs.mgz" # list2env(list(transform = NULL, positions = NULL, zoom = 1, pixel_width = 0.5, @@ -112,7 +115,7 @@ plot_slices <- function( more_args$main <- '' more_args$x <- x more_args$y <- x - more_args$add <- FALSE + # more_args$add <- FALSE oldpar <- graphics::par(no.readonly = TRUE) @@ -123,39 +126,42 @@ plot_slices <- function( } nc <- min(max(round(nc), 1), npts) nr <- ceiling(npts / nc) - if( title_position == "left") { - lmat <- matrix(seq_len(nr * nc), ncol = nc, byrow = FALSE) - lmat <- t(apply(lmat, 1, function(l) { - l <- (l - 1) * 4 - as.vector(rbind(l + 1, l + 2, l + 3, l + 4)) - })) - dim(lmat) <- c(nr, nc * 4) - graphics::layout( - lmat, - widths = rep(c(graphics::lcm(0.8), 1, 1, 1), times = nc) - ) - } else { - lmat <- matrix(seq_len(nr * nc), ncol = nc, byrow = TRUE) - lmat <- apply(lmat, 2, function(l) { - l <- (l - 1) * 4 - c(rep(l + 1, each = 3), t(outer(l, c(2,3,4), FUN = "+"))) - }) - dim(lmat) <- c(nr * 3, nc * 2) - lmat <- t(lmat) - graphics::layout( - lmat, - heights = rep(c(graphics::lcm(0.8), 1), times = nc) + if(!length(which)) { + if( title_position == "left") { + lmat <- matrix(seq_len(nr * nc), ncol = nc, byrow = FALSE) + lmat <- t(apply(lmat, 1, function(l) { + l <- (l - 1) * 4 + as.vector(rbind(l + 1, l + 2, l + 3, l + 4)) + })) + dim(lmat) <- c(nr, nc * 4) + graphics::layout( + lmat, + widths = rep(c(graphics::lcm(0.8), 1, 1, 1), times = nc) + ) + } else { + lmat <- matrix(seq_len(nr * nc), ncol = nc, byrow = TRUE) + lmat <- apply(lmat, 2, function(l) { + l <- (l - 1) * 4 + c(rep(l + 1, each = 3), t(outer(l, c(2,3,4), FUN = "+"))) + }) + dim(lmat) <- c(nr * 3, nc * 2) + lmat <- t(lmat) + graphics::layout( + lmat, + heights = rep(c(graphics::lcm(0.8), 1), times = nc) + ) + } + + graphics::par( + bg = pal[[1]], + fg = pal[[length(pal)]], + col.main = pal[[length(pal)]], + col.axis = pal[[1]], + mar = c(0,0,0,0) ) + on.exit({ do.call(graphics::par, oldpar) }) } - graphics::par( - bg = pal[[1]], - fg = pal[[length(pal)]], - col.main = pal[[length(pal)]], - col.axis = pal[[1]], - mar = c(0,0,0,0) - ) - on.exit({ do.call(graphics::par, oldpar) }) # Calculate plt pin <- graphics::par("din") @@ -188,58 +194,69 @@ plot_slices <- function( } lapply(seq_len(npts), function(ii) { + pos_pt <- c(positions[ii, ], 0) adjust_plt(reset = TRUE) - graphics::plot.new() - if(title_position == "top") { - graphics::mtext(side = 1, line = -1, text = main[[ii]], las = 0) - } else { - graphics::mtext(side = 4, line = -1.5, text = main[[ii]], las = 0) + + if(!length(which)) { + graphics::plot.new() + if(title_position == "top") { + graphics::mtext(side = 1, line = -1, text = main[[ii]], las = 0) + } else { + graphics::mtext(side = 4, line = -1.5, text = main[[ii]], las = 0) + } } - # Axial - # translate x transform_inv x translate^-1 x Norig - IJK <- round(world2ijk[c(1, 2, 3), ] %*% (transform_inv %*% pos + pos_pt)) + 1L - sel <- IJK[1,] > shape[[1]] | IJK[2,] > shape[[2]] | IJK[3,] > shape[[3]] - IJK[,sel] <- NA - IJK[IJK < 1] <- NA - idx <- t(IJK - 1) %*% cumshape + 1 - slice <- nu(volume$data[idx]) - - dim(slice) <- c(nx, nx) - more_args$z <- slice - adjust_plt() - do.call(graphics::image, more_args) - panel_last( ii, 1 ) - - # Sagittal - IJK <- round(world2ijk[c(1, 2, 3), ] %*% (pos[c(3,1,2,4), , drop = FALSE] + pos_pt)) + 1L - sel <- IJK[1,] > shape[[1]] | IJK[2,] > shape[[2]] | IJK[3,] > shape[[3]] - IJK[,sel] <- NA - IJK[IJK < 1] <- NA - idx <- t(IJK - 1) %*% cumshape + 1 - slice <- nu(volume$data[idx]) - - dim(slice) <- c(nx, nx) - more_args$z <- slice - adjust_plt() - do.call(graphics::image, more_args) - panel_last( ii, 2 ) - - # Coronal - IJK <- round(world2ijk[c(1, 2, 3), ] %*% (pos[c(1,3,2,4), , drop = FALSE] + pos_pt)) + 1L - sel <- IJK[1,] > shape[[1]] | IJK[2,] > shape[[2]] | IJK[3,] > shape[[3]] - IJK[,sel] <- NA - IJK[IJK < 1] <- NA - idx <- t(IJK - 1) %*% cumshape + 1 - slice <- nu(volume$data[idx]) - - dim(slice) <- c(nx, nx) - more_args$z <- slice - adjust_plt() - do.call(graphics::image, more_args) - panel_last( ii, 3 ) + if(!length(which) || 1 %in% which) { + # Axial + # translate x transform_inv x translate^-1 x Norig + IJK <- round(world2ijk[c(1, 2, 3), ] %*% (transform_inv %*% pos + pos_pt)) + 1L + sel <- IJK[1,] > shape[[1]] | IJK[2,] > shape[[2]] | IJK[3,] > shape[[3]] + IJK[,sel] <- NA + IJK[IJK < 1] <- NA + idx <- t(IJK - 1) %*% cumshape + 1 + slice <- nu(volume$data[idx]) + + dim(slice) <- c(nx, nx) + more_args$z <- slice + adjust_plt() + do.call(graphics::image, more_args) + panel_last( ii, 1 ) + } + + + if(!length(which) || 2 %in% which) { + # Sagittal + IJK <- round(world2ijk[c(1, 2, 3), ] %*% (pos[c(3,1,2,4), , drop = FALSE] + pos_pt)) + 1L + sel <- IJK[1,] > shape[[1]] | IJK[2,] > shape[[2]] | IJK[3,] > shape[[3]] + IJK[,sel] <- NA + IJK[IJK < 1] <- NA + idx <- t(IJK - 1) %*% cumshape + 1 + slice <- nu(volume$data[idx]) + + dim(slice) <- c(nx, nx) + more_args$z <- slice + adjust_plt() + do.call(graphics::image, more_args) + panel_last( ii, 2 ) + } + + if(!length(which) || 3 %in% which) { + # Coronal + IJK <- round(world2ijk[c(1, 2, 3), ] %*% (pos[c(1,3,2,4), , drop = FALSE] + pos_pt)) + 1L + sel <- IJK[1,] > shape[[1]] | IJK[2,] > shape[[2]] | IJK[3,] > shape[[3]] + IJK[,sel] <- NA + IJK[IJK < 1] <- NA + idx <- t(IJK - 1) %*% cumshape + 1 + slice <- nu(volume$data[idx]) + + dim(slice) <- c(nx, nx) + more_args$z <- slice + adjust_plt() + do.call(graphics::image, more_args) + panel_last( ii, 3 ) + } NULL }) diff --git a/cran-comments.md b/cran-comments.md index 4f3446ec..ae3406fc 100644 --- a/cran-comments.md +++ b/cran-comments.md @@ -1,9 +1,22 @@ # Update logs (for R-cran only) -## 2023-07-03 -**Version 1.0.1 (current)** +## 2024-02-06 +**Version 1.0.2 (current)** + +Self check: 0 errors | 0 warnings | 1 note + +``` +❯ checking installed package size ... NOTE + installed size is 5.4Mb + sub-directories of 1Mb or more: + R 2.0Mb + threeBrainJS 1.8Mb +``` -Self check: 0 errors | 0 warnings | 0 notes +I (tried, but) can't reduce the size anymore from `JavaScript` engine and package functions. + +## 2023-07-03 +**Version 1.0.1 (passed)** ## 2023-06-03 **Version 1.0.0 (passed)** @@ -23,13 +36,6 @@ Self check: 0 errors | 0 warnings | 0 notes ## 2021-12-02 **Version 0.2.4 (passed)** -Starting from this version, the package size will be greater than `5Mb`, which will trigger the check note. However, I can't reduce the size since most are from the `JavaScript` engine. - -``` -checking installed package size ... NOTE - installed size is 5.3Mb -``` - ## 2021-10-13 **Version 0.2.3 (passed)** diff --git a/inst/WORDLIST b/inst/WORDLIST index e3424a64..1a6cbe21 100644 --- a/inst/WORDLIST +++ b/inst/WORDLIST @@ -1,11 +1,13 @@ AFNI AbstractGeom +Affine BRIK CMD CRS DICOM DOI ECoG +Eneuro FREESURFER FreeSurfer GeomGroup @@ -15,6 +17,7 @@ JS JSON Localizer MNI +MPL NIFTI NeuroImage Nifti @@ -28,6 +31,7 @@ Threejs UI WebGL XYZ +YAEL asc autorecon commissure @@ -52,6 +56,7 @@ neuroimage nibabel nii npx +parcellation pial px readgii diff --git a/man/plot_slices.Rd b/man/plot_slices.Rd index ffd09861..034a2c8c 100644 --- a/man/plot_slices.Rd +++ b/man/plot_slices.Rd @@ -18,6 +18,7 @@ plot_slices( title_position = c("left", "top"), fun = NULL, nc = NA, + which = NULL, ... ) } @@ -54,6 +55,10 @@ image is drawn; can be used to draw cross-hairs or annotate each image} \item{nc}{number of "columns" in the plot when there are too many positions, must be positive integer; default is \code{NA} (automatically determined)} +\item{which}{which plane to plot; default is \code{NULL}, which will trigger +new plots and add titles; set to \code{1} for 'Axial' plane, \code{2} for +'Sagittal', and \code{3} for 'Coronal'.} + \item{...}{additional arguments passing into \code{\link[graphics]{image}}} } \value{