#' Definition for electrodes that collects continuous signals
#' @description Defines class of electrodes that record continuous signals.
#' These signals have meaningful power density and will be transformed for
#' time-frequency analysis. Unlike spike signals (discrete),
#' continuous signals usually represent signals fired from a group of neurons.
Continuous_Electrode <- R6::R6Class(
classname = 'Continuous_Electrode',
inherit = RAVEAbstarctElectrode,
cloneable = FALSE,
lock_class = TRUE,
private = list(
persisted_voltage_unref = NULL,
persisted_power_unref = NULL,
persisted_phase_unref = NULL,
persisted_coef_ref = NULL,
referenced_power_cache_file = character(0),
referenced_phase_cache_file = character(0),
referenced_voltage_cache_file = character(0)
),
public = list(
#' @field type type of signals collected by electrode
type = 'Continuous',
#' @description set reference for current electrode
#' @param reference either \code{NULL} or \code{Continuous_Electrode} instance
set_reference = function(reference){
self$.set_reference(reference)
},
#' @description constructor
#' @param subject,number,is_reference see constructor in
#' \code{\link{RAVEAbstarctElectrode}}
initialize = function(subject, number, is_reference = FALSE){
super$initialize(subject, number, is_reference)
if(is_reference){
# this is a reference electrode
self$is_reference <- TRUE
ref_electrodes <- stringr::str_match(number, 'ref_([0-9\\-,\\ ]+)')[,2]
# no reference value, 'noref'
if(is.na(ref_electrodes)){
ref_electrodes <- ''
}
e <- dipsaus::parse_svec(ref_electrodes)
if(length(e) == 0){
self$number <- 'noref'
} else {
if(length(e) == 1){
self$number <- e
} else {
# check subject reference directory
self$number <- sprintf('ref_%s.h5', ref_electrodes)
if(!file.exists(file.path(self$subject$reference_path, self$number))){
rave_warn("Reference file {self$number} is missing")
}
}
}
}
},
# data method
#' @description load voltage data that is before referenced
#' @param block experiment block
#' @param persist whether to persist in the instance, default is false,
#' however, if this function will be called multiple times, set it to true.
#' @return voltage data before reference
load_unreferenced_voltage = function(block, persist = FALSE){
stopifnot2(block %in% self$subject$blocks, msg = sprintf(
'Block %s doesn not exists', block
))
stopifnot2(self$exists, msg = sprintf('Electrode %s is invalid', self$number))
if(persist){
if(!is.null(private$persisted_voltage_unref[[block]])){
return(private$persisted_voltage_unref[[block]])
}
}
if(is.numeric(self$number)){
# load from data
fst_path <- file.path(self$subject$cache_path, 'voltage', 'raw', block, self$fst_fname)
if(!file.exists(fst_path) && isTRUE(self$cached_reference == 'noref')){
fst_path <- file.path(self$subject$cache_path, 'voltage', 'ref', block, self$fst_fname)
}
h5_path <- file.path(self$subject$data_path, 'voltage', self$h5_fname)
h5_name <- sprintf('/raw/voltage/%s', block)
re <- load_fst_or_h5(
fst_path = fst_path,
h5_path = h5_path, h5_name = h5_name,
fst_need_transpose = TRUE,
fst_need_drop = TRUE,
ram = FALSE
)
} else {
if(self$number == 'noref'){
return(0)
}
# load from reference folder
h5_path <- file.path(self$subject$reference_path, self$h5_fname)
h5_name <- sprintf('/voltage/%s', block)
re <- load_h5(h5_path, h5_name, read_only = TRUE, ram = FALSE)
}
if(persist){
private$persisted_voltage_unref[[block]] <- re[]
return(private$persisted_voltage_unref[[block]])
}
re
},
#' @description load power data that is before referenced
#' @param block experiment block
#' @param persist whether to persist in the instance, default is false,
#' however, if this function will be called multiple times, set it to true.
#' @return power data before reference
load_unreferenced_power = function(block, persist = FALSE){
stopifnot2(block %in% self$subject$blocks, msg = sprintf(
'Block %s doesn not exists', block
))
stopifnot2(self$exists, msg = sprintf('Electrode %s is invalid', self$number))
if(persist){
if(!is.null(private$persisted_power_unref[[block]])){
return(private$persisted_power_unref[[block]])
}
}
if(is.numeric(self$number)){
# load from data
fst_path <- file.path(self$subject$cache_path, 'power', 'raw', block, self$fst_fname)
if(!file.exists(fst_path) && isTRUE(self$cached_reference == 'noref')){
fst_path <- file.path(self$subject$cache_path, 'power', 'ref', block, self$fst_fname)
}
h5_path <- file.path(self$subject$data_path, 'power', self$h5_fname)
h5_name <- sprintf('/raw/power/%s', block)
re <- load_fst_or_h5(
fst_path = fst_path,
h5_path = h5_path, h5_name = h5_name,
fst_need_transpose = TRUE,
fst_need_drop = TRUE,
ram = FALSE
)
} else {
if(self$number == 'noref'){
return(0)
}
if(!persist){
rave_fatal('load_unreferenced_power is not for reference_electrode unless persist is TRUE')
}
# load from reference folder
h5_path <- file.path(self$subject$reference_path, self$h5_fname)
h5_name <- sprintf('/wavelet/coef/%s', block)
re <- load_h5(h5_path, h5_name, read_only = TRUE, ram = TRUE)
re <- re[,,1, drop = FALSE] ^ 2
dim(re) <- dim(re)[1:2]
private$persisted_power_unref[[block]] <- re
return(re)
}
if(persist){
private$persisted_power_unref[[block]] <- re[]
return(private$persisted_power_unref[[block]])
}
re
},
#' @description load phase data that is before referenced
#' @param block experiment block
#' @param persist whether to persist in the instance, default is false,
#' however, if this function will be called multiple times, set it to true.
#' @return phase data before reference
load_unreferenced_phase = function(block, persist = FALSE){
stopifnot2(block %in% self$subject$blocks, msg = sprintf(
'Block %s doesn not exists', block
))
stopifnot2(self$exists, msg = sprintf('Electrode %s is invalid', self$number))
if(persist){
if(!is.null(private$persisted_phase_unref[[block]])){
return(private$persisted_phase_unref[[block]])
}
}
if(is.numeric(self$number)){
# load from data
fst_path <- file.path(self$subject$cache_path, 'phase', 'raw', block, self$fst_fname)
if(!file.exists(fst_path) && isTRUE(self$cached_reference == 'noref')){
fst_path <- file.path(self$subject$cache_path, 'phase', 'ref', block, self$fst_fname)
}
h5_path <- file.path(self$subject$data_path, 'phase', self$h5_fname)
h5_name <- sprintf('/raw/phase/%s', block)
re <- load_fst_or_h5(
fst_path = fst_path,
h5_path = h5_path, h5_name = h5_name,
fst_need_transpose = TRUE,
fst_need_drop = TRUE,
ram = FALSE
)
} else {
if(self$number == 'noref'){
return(0)
}
if(!persist){
rave_fatal('load_unreferenced_phase is not for reference_electrode unless persist is TRUE')
}
# load from reference folder
h5_path <- file.path(self$subject$reference_path, self$h5_fname)
h5_name <- sprintf('/wavelet/coef/%s', block)
re <- load_h5(h5_path, h5_name, read_only = TRUE, ram = TRUE)
re <- re[,,2, drop = FALSE]
dim(re) <- dim(re)[1:2]
private$persisted_phase_unref[[block]] <- re
return(re)
}
if(persist){
private$persisted_phase_unref[[block]] <- re[]
return(private$persisted_phase_unref[[block]])
}
re
},
# reference
#' @description reference power for given block
#' @param block character, experiment block
#' @return referenced power
reference_power = function(block){
# check whether the reference has been cached
stopifnot2(!self$is_reference, msg = 'Cannot reference a reference ;D')
if(is.null(self$reference) || isTRUE(self$reference$number == 'noref') ){
# noref!
return(self$load_unreferenced_power(block, persist = FALSE)[])
}
# check whether cached
has_cached <- self$reference_equals_cached
if( has_cached ){
fst_path <- file.path(self$subject$cache_path, 'power', 'ref', block, self$fst_fname)
h5_path <- file.path(self$subject$data_path, 'power', self$h5_fname)
h5_name <- sprintf('/ref/power/%s', block)
re <- load_fst_or_h5(
fst_path = fst_path,
h5_path = h5_path, h5_name = h5_name,
fst_need_transpose = TRUE,
fst_need_drop = TRUE,
ram = FALSE
)
} else {
# calculate reference by yourself
power <- self$load_unreferenced_power(block = block, persist = FALSE)
phase <- self$load_unreferenced_phase(block = block, persist = FALSE)
ref_power <- self$reference$load_unreferenced_power(block = block, persist = TRUE)
ref_phase <- self$reference$load_unreferenced_phase(block = block, persist = TRUE)
coef <- sqrt(power[]) * exp(phase[] * 1i) - sqrt(ref_power) * exp(ref_phase * 1i)
re <- Mod(coef) ^ 2
}
re
},
#' @description reference phase for given block
#' @param block character, experiment block
#' @return referenced phase
reference_phase = function(block){
# check whether the reference has been cached
stopifnot2(!self$is_reference, msg = 'Cannot reference a reference ;D')
if(is.null(self$reference) || isTRUE(self$reference$number == 'noref') ){
# noref!
return(self$load_unreferenced_phase(block, persist = FALSE)[])
}
has_cached <- self$reference_equals_cached
if( has_cached ){
fst_path <- file.path(self$subject$cache_path, 'phase', 'ref', block, self$fst_fname)
h5_path <- file.path(self$subject$data_path, 'phase', self$h5_fname)
h5_name <- sprintf('/ref/phase/%s', block)
re <- load_fst_or_h5(
fst_path = fst_path,
h5_path = h5_path, h5_name = h5_name,
fst_need_transpose = TRUE,
fst_need_drop = TRUE,
ram = FALSE
)
} else {
# calculate reference by yourself
power <- self$load_unreferenced_power(block = block, persist = FALSE)
phase <- self$load_unreferenced_phase(block = block, persist = FALSE)
ref_power <- self$reference$load_unreferenced_power(block = block, persist = TRUE)
ref_phase <- self$reference$load_unreferenced_phase(block = block, persist = TRUE)
coef <- sqrt(power[]) * exp(phase[] * 1i) - sqrt(ref_power) * exp(ref_phase * 1i)
re <- Arg(coef)
}
re
},
#' @description reference voltage for given block
#' @param block character, experiment block
#' @return referenced voltage
reference_voltage = function(block){
# check whether the reference has been cached
stopifnot2(!self$is_reference, msg = 'Cannot reference a reference ;D')
if(is.null(self$reference) || isTRUE(self$reference$number == 'noref') ){
# noref!
return(self$load_unreferenced_voltage(block, persist = FALSE)[])
}
has_cached <- self$reference_equals_cached
if( has_cached ){
fst_path <- file.path(self$subject$cache_path, 'voltage', 'ref', block, self$fst_fname)
h5_path <- file.path(self$subject$data_path, 'voltage', self$h5_fname)
h5_name <- sprintf('/ref/voltage/%s', block)
re <- load_fst_or_h5(
fst_path = fst_path,
h5_path = h5_path, h5_name = h5_name,
fst_need_transpose = TRUE,
fst_need_drop = TRUE,
ram = FALSE
)
} else {
# calculate reference by yourself
voltage <- self$load_unreferenced_voltage(block = block, persist = FALSE)
ref_voltage <- self$reference$load_unreferenced_voltage(block = block, persist = TRUE)
re <- voltage-ref_voltage
}
re
},
#' @description check whether power is cached in run-time set-ups
#' @param before_onset seconds before trial onset
#' @param after_onset seconds after trial onset
#' @param dtype data type, choices are 'power', 'phase', and 'voltage'
#' @return logical whether power has been cached in a \code{lazyarray}
is_cached = function(before_onset, after_onset, dtype = c('power', 'phase', 'voltage')){
dtype <- match.arg(dtype)
# Repo signature will not be checked
dinfo <- self$.dimension(before_onset = before_onset, after_onset = after_onset, dtype = dtype)
tmp <- sprintf('pre__%.4f_post_%.4f', before_onset, after_onset)
fst_cache_dir <- file.path(self$cache_path, dtype, tmp)
fst_cache_file <- file.path(fst_cache_dir, self$fst_fname)
fst_cache_meta <- file.path(fst_cache_dir, 'lazyarray.meta')
if(file.exists(fst_cache_file) && file.exists(fst_cache_meta)){
arr <- lazyarray::load_lazyarray(fst_cache_dir, read_only = TRUE)
return(all(dim(arr) - dinfo$dim == 0))
} else {
return(FALSE)
}
},
#' @description internally used to determine data dimension and names
#' @param before_onset seconds before trial onset
#' @param after_onset seconds after trial onset
#' @param dtype data type, choices are 'power', 'phase', and 'voltge'
.dimension = function(before_onset, after_onset, dtype = c('power', 'phase', 'voltage')){
dtype <- match.arg(dtype)
stopifnot2(inherits(self$epoch, 'RAVEEpoch'), msg = 'You need to set electrode epoch first')
preproc <- self$preprocess_info
dnames <- list()
dim <- rep(NA_integer_, 4L)
dim[[1]] <- self$epoch$n_trials
dnames$Trial <- self$epoch$trials
if(dtype == 'voltage'){
srate <- preproc$sample_rate
} else {
stopifnot2(isTRUE(preproc$has_wavelet), msg = sprintf('Electrode %s does not have power data', self$number))
wave_info <- self$subject$preprocess_settings$wavelet_params
srate <- wave_info$downsample_to
dim[[2]] <- length(wave_info$frequencies)
dnames$Frequency <- wave_info$frequencies
}
tidx <- seq.int(- ceiling(before_onset * srate), ceiling(after_onset * srate))
dim[[3]] <- length(tidx)
dnames$Time <- tidx / srate
all_electrodes <- seq_len(max(self$subject$electrodes))
dim[[4]] <- length(all_electrodes)
dnames$Electrode <- all_electrodes
dim <- dim[!is.na(dim)]
list(
dim = dim,
dimnames = dnames,
srate = srate,
tidx = tidx
)
},
#' @description perform epoch on power with epoch and reference
#' @param before_onset seconds before trial onset
#' @param after_onset seconds after trial onset
#' @return An \code{lazyarray} object that can be subset like normal arrays
epoch_power = function(before_onset, after_onset){
dinfo <- self$.dimension(before_onset = before_onset, after_onset = after_onset, dtype = 'power')
tbl = self$epoch$table
trials = self$epoch$trials
blk = unique(tbl$Block)
power_srate <- dinfo$srate
tidx = dinfo$tidx
tmp <- sprintf('pre__%.4f_post_%.4f', before_onset, after_onset)
dir <- file.path(self$cache_path, 'power', tmp)
array <- lazyarray::lazyarray(dir, storage_format = 'double',
dim = dinfo$dim, dimnames = dinfo$dimnames,
quiet = TRUE, read_only = FALSE)
dim <- dim(array)
electrode_idx <- self$number
private$referenced_power_cache_file <- array$get_partition_fpath(electrode_idx, full_path = TRUE)
if(file.exists(private$referenced_power_cache_file)){
# temporarily cached
rave_debug('Partition exists - electrode {self$number}')
return(lazyarray::load_lazyarray(path = dir, read_only = TRUE))
}
# else epoch
results <- array(NA_real_, dinfo$dim[c(1,2,3)])
for(b in blk){
# print(b)
ref_power <- self$reference_power(b)
# find which trials in the block
sel <- tbl$Block %in% b
sub_tbl <- tbl[sel, ]
slice_idx <- vapply(seq_len(nrow(sub_tbl)), function(ii){
row <- sub_tbl[ii,]
t_pos <- round(row$Time * power_srate)
as.integer(t_pos + tidx)
}, FUN.VALUE = tidx)
# freq x time x trial
ref_power <- ref_power[, as.vector(slice_idx)]
dim(ref_power) <- c(dim[c(2, 3)], sum(sel))
results[sel,,] <- aperm(ref_power, c(3,1,2))
}
# write to array
array[,,,electrode_idx] <- results
return(lazyarray::load_lazyarray(path = dir, read_only = TRUE))
},
#' @description perform epoch on phase with epoch and reference
#' @param before_onset seconds before trial onset
#' @param after_onset seconds after trial onset
#' @return An \code{lazyarray} object that can be subset like normal arrays
epoch_phase = function(before_onset, after_onset){
dinfo <- self$.dimension(before_onset = before_onset, after_onset = after_onset, dtype = 'phase')
tbl = self$epoch$table
trials = self$epoch$trials
blk = unique(tbl$Block)
phase_srate <- dinfo$srate
tidx = dinfo$tidx
tmp <- sprintf('pre__%.4f_post_%.4f', before_onset, after_onset)
dir <- file.path(self$cache_path, 'phase', tmp)
array <- lazyarray::lazyarray(dir, storage_format = 'double',
dim = dinfo$dim, dimnames = dinfo$dimnames,
quiet = TRUE, read_only = FALSE)
dim <- dim(array)
electrode_idx <- self$number
private$referenced_phase_cache_file <- array$get_partition_fpath(electrode_idx, full_path = TRUE)
if(file.exists(private$referenced_phase_cache_file)){
# temporarily cached
rave_debug('Partition exists - electrode {self$number}')
return(lazyarray::load_lazyarray(path = dir, read_only = TRUE))
}
# else epoch
results <- array(NA_real_, dinfo$dim[c(1,2,3)])
for(b in blk){
ref_phase <- self$reference_phase(b)
# find which trials in the block
sel <- tbl$Block %in% b
sub_tbl <- tbl[sel, ]
slice_idx <- vapply(seq_len(nrow(sub_tbl)), function(ii){
row <- sub_tbl[ii,]
t_pos <- round(row$Time * phase_srate)
as.integer(t_pos + tidx)
}, FUN.VALUE = tidx)
# freq x time x trial
ref_phase <- ref_phase[, as.vector(slice_idx)]
dim(ref_phase) <- c(dim[c(2, 3)], sum(sel))
results[sel,,] <- aperm(ref_phase, c(3,1,2))
}
# write to array
array[,,,electrode_idx] <- results
return(lazyarray::load_lazyarray(path = dir, read_only = TRUE))
},
#' @description perform epoch on phase with epoch and reference
#' @param before_onset seconds before trial onset
#' @param after_onset seconds after trial onset
#' @return An \code{lazyarray} object that can be subset like normal arrays
epoch_voltage = function(before_onset, after_onset){
dinfo <- self$.dimension(before_onset = before_onset, after_onset = after_onset, dtype = 'voltage')
tbl = self$epoch$table
trials = self$epoch$trials
blk = unique(tbl$Block)
voltage_srate <- dinfo$srate
tidx = dinfo$tidx
tmp <- sprintf('pre__%.4f_post_%.4f', before_onset, after_onset)
dir <- file.path(self$cache_path, 'voltage', tmp)
array <- lazyarray::lazyarray(dir, storage_format = 'double',
dim = dinfo$dim, dimnames = dinfo$dimnames,
quiet = TRUE, read_only = FALSE)
dim <- dim(array)
electrode_idx <- self$number
private$referenced_voltage_cache_file <- array$get_partition_fpath(electrode_idx, full_path = TRUE)
if(file.exists(private$referenced_voltage_cache_file)){
# temporarily cached
rave_debug('Partition exists - electrode {self$number}')
return(lazyarray::load_lazyarray(path = dir, read_only = TRUE))
}
# else epoch
results <- array(NA_real_, dinfo$dim[c(1,2)])
for(b in blk){
ref_voltage <- self$reference_voltage(b)
# find which trials in the block
sel <- tbl$Block %in% b
sub_tbl <- tbl[sel, ]
slice_idx <- vapply(seq_len(nrow(sub_tbl)), function(ii){
row <- sub_tbl[ii,]
t_pos <- round(row$Time * voltage_srate)
as.integer(t_pos + tidx)
}, FUN.VALUE = tidx)
# freq x time x trial
ref_voltage <- ref_voltage[as.vector(slice_idx)]
dim(ref_voltage) <- c(dim[2], sum(sel))
results[sel,] <- t(ref_voltage)
}
# write to array
array[,,electrode_idx] <- results
return(lazyarray::load_lazyarray(path = dir, read_only = TRUE))
},
#' @description method to clear cache on hard drive
#' @param ... ignored
clear_cache = function(...){
f <- private$referenced_power_cache_file
if(length(f) && file.exists(f)){
unlink(f)
}
},
#' @description method to clear memory
#' @param ... ignored
clear_memory = function(...){
private$persisted_voltage_unref <- NULL
private$persisted_power_unref <- NULL
private$persisted_phase_unref <- NULL
private$persisted_coef_ref <- NULL
if(inherits(self$reference, 'RAVEAbstarctElectrode')){
self$reference$clear_memory()
}
}
),
active = list(
#' @field exists whether electrode exists in subject
exists = function(){
if(!self$is_reference || is.numeric(self$number)){
super$exists
} else if( isTRUE(self$number == 'noref') ) {
return(TRUE)
} else {
file.exists(file.path(self$subject$reference_path, self$number))
}
},
#' @field h5_fname 'HDF5' file name
h5_fname = function(){
if(is.character(self$number)){
self$number
} else {
sprintf('%s.h5', self$number)
}
},
#' @field fst_fname 'FST' file name
fst_fname = function(){
if(is.character(self$number)){
self$number
} else {
sprintf('%s.fst', self$number)
}
},
#' @field reference_equals_cached whether referenced data has been cached
#' in 'RAVE' folder
reference_equals_cached = function(){
if(self$is_reference){
return(TRUE)
}
ref <- self$reference$number
if(is.numeric(ref)){
ref <- sprintf('ref_%.0f', ref)
}
has_cached <- any(sprintf('%s%s', self$cached_reference, c('', '.h5')) %in% ref)
has_cached
},
#' @field valid whether current electrode is valid: subject exists and
#' contains current electrode or reference; subject electrode type matches
#' with current electrode type
valid = function(){
if(!self$exists) {return(FALSE)}
if(self$is_reference) {return(TRUE)}
elec <- self$subject$electrodes
if(!self$number %in% elec){ return(FALSE) }
# type matches with subject
if(!isTRUE(self$subject$electrode_types[elec %in% self$number] == self$type)){
return(FALSE)
}
return(TRUE)
},
#' @field raw_sample_rate voltage sample rate
raw_sample_rate = function(){
elec <- self$subject$electrodes
srate <- self$subject$raw_sample_rates[elec %in% self$number]
if(!length(srate)){
srate <- NA
}
srate
},
#' @field power_sample_rate power/phase sample rate
power_sample_rate = function(){
elec <- self$subject$electrodes
srate <- self$subject$power_sample_rate[elec %in% self$number]
if(!length(srate)){
srate <- NA
}
srate
},
#' @field preprocess_info preprocess information
preprocess_info = function(){
self$subject$preprocess_settings$electrode_info(electrode = self$number)
}
)
)
#' Definition for electrodes that collects 'LFP' signals
#' @examples
#' \dontrun{
#'
#' # Download demo subject DemoSubject ~700MB data
#'
#' # Electrode 14
#' e <- LFP_electrode$new(subject = 'demo/DemoSubject',
#' number = 14, is_reference = FALSE)
#'
#' # Reference "ref_13-16,24"
#' ref <- LFP_electrode$new(subject = 'demo/DemoSubject',
#' number = "ref_13-16,24", is_reference = TRUE)
#'
#' # ------ Reference ------
#' # By default there is no reference
#' e$reference_name # "noref
#'
#' # set reference
#' e$set_reference(reference = ref)
#' e$reference_name # "ref_13-16,24"
#'
#' # Set epoch
#' e$set_epoch(epoch = 'auditory_onset')
#'
#' # Now epoch power
#' power <- e$epoch_power(before_onset = 1, after_onset = 2)
#'
#' # Trial x Frequency x Time x Electrodes
#' power
#'
#' # Subset power
#' subset(power, Time ~ Time < 0, Electrode ~ Electrode == 14)
#'
#' # clear memory in RAM and cache on hard disk
#' e$clear_cache()
#' e$clear_memory()
#'
#'
#' }
#' @export
LFP_electrode <- R6::R6Class(
classname = 'LFP_electrode',
inherit = Continuous_Electrode,
cloneable = FALSE,
lock_class = TRUE,
public = list(
#' @field type type of signals collected by the electrode
type = 'LFP'
)
)
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.