SOCR ≫ | TCIU Website ≫ | TCIU GitHub ≫ |
This Spacekime TCIU Learning Module presents the core elements of spacekime analytics including:
TCIU and other R
package dependencies …
In this case, we are just loading some exemplary fMRI data, which is available here.
# pathname <- file.path("./test_data", "subj1_run1_complex_all.mat")
mat1 = readMat("./test_data/subj1_run1_complex_all.mat")
bigim1 = mat1$bigim[,64:1,,]
bigim1_mod = Mod(bigim1) # Modulus
smoothmod = GaussSmoothArray(bigim1_mod,sigma = diag(3,3))
# dim(bigim1) = 64 64 40
# bigim1 contains the complex image space
# dimensions are 64x*64y*40z*160t, corresponding to x,y,z,time
load("./test_data/mask.rda") # load the 3D nifti data of the mask
load("./test_data/mask_label.rda") # load the 3D nifti data of the mask with labels
load("./test_data/mask_dict.rda") # load the label index and label name
label_index = mask_dict$index
label_name = as.character(mask_dict$name)
label_mask = mask_label
load("./test_data/hemody.rda") # load the hemodynamic contour of the brain
The TCIU
function fmri_time_series()
is
used to create four interactive time series graphs for the real,
imaginary, magnitude, and phase parts for the fMRI spacetime data. We
can also add a reference plotly object to the plot. This function is
based on the GTSplot
function from package
TSplotly
.
The following examples connect to several ongoing spacekime analytics, including kimesurface analyticity, kimesurface regularization, and distribution-time dynamics.
The first example uses a time-series simulation to illustrate how to transform the fMRI time-series at a fixed voxel location into a kimesurface (kime-series).
Notes:
Validate all steps in this time-series to kimesurfaces transformation protocol of simulated data, and finalize this 3D visualization.
Try it with real fMRI data at brain voxel locations associated with the finger-tapping task or musical genre study.
plot_ly
text labels that will be
shown on mouse hover (pop-up dialogues) over each
kimesurface/kime-series. These text-labels are stored in Cartesian
coordinates \((-10\leq x\leq 10,-10\leq y\leq
10)\), but are computed using the polar coordinates \((1\leq t\leq 10,-\pi\leq \phi<\pi)\) and
the polar-to-Cartesian transformation. The labels are \(21\times21\) arrays including the triple
\((t, \phi, fMRIvalue)\). Separate
text-labels are generated for each kimesurface (ON vs. OFF
stimuli).plot_ly
to display in 3D the kime-series as 2D
manifolds (kimesurfaces) over the Cartesian domain.Randomly generate \(8\) \(phi=\phi\) kime-phases for each of the \(10\) time radii. This yields an \(8\times 10\) array (phi_8_vec) of kime phase directions. These directions can be obtained by different strategies, e.g., (1) uniform or Laplace distribution sampling over the interval \([-\pi:\pi)\); (2) randomly sampling with/without replacement from known kime-phases obtained from similar processes; (3) use an \(AR(p)\) autoregressive model with memory (affected by prior samples at earlier \(p\) times), etc.
Optionally, order all kime-phases (rows) from small to large for each column.
# plot Laplacian
# ggfortify::ggdistribution(extraDistr::dlaplace, seq(-pi, pi, 0.01), m=0, s=0.5)
t <- seq(-pi, pi, 0.01)
f_t <- extraDistr::dlaplace(t, mu=0, sigma=0.5)
plot_ly(x=t, y=f_t, type="scatter", mode="lines", name="Laplace Distribution") %>%
layout(title="Laplace Distribution: Kime-Phase Prior")
# randomly generate 8 phi kime-phases for each of the 10 time radii
phi_8_vec <- matrix(NA, ncol=10, nrow = 8)
for (t in 1:10) {
# for a given t, generate 8 new phases
set.seed(t);
phi_8_vec[ ,t] <-
extraDistr::rlaplace(8, mu=0, sigma=0.5)
# rank-order the phases for consistency
# within the same foliation leaf
phi_8_vec[ ,t] <- sort(phi_8_vec[ ,t])
# force phases in [-pi: pi)
for (i in 1:8) {
if (phi_8_vec[i,t] < -pi)
phi_8_vec[i,t] <- -pi
if (phi_8_vec[i,t] >= pi)
phi_8_vec[i,t] <- pi
}
}
# order all kime-phases (rows) from small to large for each column
# phi_8_vec_ordered <- apply(phi_8_vec, 2, sort)
Here should be included any study-specific data preprocessing. In the case of fMRI series, we may need to split off the individual repeated measurement fMRI time-series from the master (single) fMRI time-series according to the specific event-related fMRI design.
For simplicity, consider a simulated binary stimulus paradigm (e.g., activation (ON) and rest (OFF) event-related design). We can split the \(160\)-timepoint fMRI series into \(80\) ON (Activation) and \(80\) OFF (rest) states, or sub-series, consisting of \(8\) repeats, each of length \(10\) time points, where each time point corresponds to about \(2.5\ s\) of clock time.
In practice, some spatial smoothing (blurring) the 1D time-series or their 2D array (tensor representations as 2D matrices (\(row=repeat\), \(column=time\)) to reduce noise and make the data more natural (low-pass filtering, avoiding high-pitch noise). Sometimes, we may also need to temper the magnitude of the raw time-series intensities, which can have a large range.
# Convert the long DF representing fMRI_ON and fMRI_OFF from polar coordinates to Cartesian coordinates
library(spatstat)
matrix_ON <- matrix(0, nrow = 21, ncol = 21)
matrix_OFF <- matrix(0, nrow = 21, ncol = 21)
for (t in 1:10) {
for (p in 1:8) {
x = 11+t*cos(phi_8_vec[p,t])
y = 11+t*sin(phi_8_vec[p,t])
matrix_ON[x,y] <- fMRI_ON[(p-1)*10 +t] # What is matrix_ON?
matrix_OFF[x,y] <- fMRI_OFF[(p-1)*10 +t]
}
}
# smooth/blur the matrices
# matrix_ON_smooth <- (1/10000)*as.matrix(blur(as.im(matrix_ON), sigma=0.5))
# matrix_OFF_smooth <- (1/10000)*as.matrix(blur(as.im(matrix_OFF), sigma=0.5))
matrix_ON_smooth <- (1/10000)*as.matrix(blur(as.im(matrix_ON), sigma=0.5))
matrix_OFF_smooth <- (1/10000)*as.matrix(blur(as.im(matrix_OFF), sigma=0.5))
plotly
labelsGenerate the plot_ly
text labels that will be shown over
the graph, upon mouse hovering (pop-up dialogues) over each
kimesurface/kime-series. These text-labels are stored in Cartesian
coordinates \((-10\leq x\leq 10,-10\leq y\leq
10)\), but are computed using the polar coordinates \((1\leq t\leq 10,-\pi\leq \phi<\pi)\) and
the polar-to-Cartesian transformation. The labels are \(21\times 21\) arrays including the triple
\((t, \phi, fMRIvalue)\). Separate
text-labels are generated for each kimesurface (ON vs. OFF
stimuli).
# fix the plot_ly Text Labels
x <- vector()
y <- vector()
i <- 1
for (t in 1:10) {
for (p in 1:8) {
x[i] = 11+t*cos(phi_8_vec[p,t])
y[i] = 11+t*sin(phi_8_vec[p,t])
i <- i+1
}
}
# hoverText <- cbind(x=1:21, y=1:21, height=as.vector(t(matrix_ON_smooth))) # tail(mytext)
# custom_txt <- matrix(NA, nrow=21, ncol=21)
# hoverTextOFF <- cbind(x=1:21, y=1:21, height=as.vector(t(matrix_OFF_smooth))) # tail(mytext)
# custom_txtOFF <- matrix(NA, nrow=21, ncol=21)
#
# for (x in 1:21) {
# for (y in 1:21) {
# t = sqrt((x-11)^2 + (y-11)^2)
# p = atan2(y-11, x-11)
# custom_txt[x,y] <- paste(' fMRI: ', round(hoverText[(x-1)*21+y, 3], 3),
# '\n time: ', round(t, 0),
# '\n phi: ', round(p, 2))
# custom_txtOFF[x,y] <- paste(' fMRI: ', round(hoverTextOFF[(x-1)*21+y, 3], 3),
# '\n time: ', round(t, 0),
# '\n phi: ', round(p, 2))
# }
# }
Interpolate the fMRI intensities, natively anchored at polar (kime) coordinates) \(\left (\underbrace{t}_{time},\underbrace{\phi}_{repeat} \right)\), into Cartesian coordinates \(fMRI(x,y), \forall -11\leq x,y \leq 11, x^2+y^2\leq 10\). Note that for specificity, we hard-coded polar-grid parameterization to time \(t\in\{1,2,3, \cdots,10\},\ |T|=10\) and phase \(\phi=seq\left (from=-\pi, to=\pi, step=\frac{2\pi}{20}\right )\in\{-3.1415927, -2.8274334, \cdots, 0.0,\cdots, 2.8274334, 3.1415927 \},\ |\Phi|=21\).
# xx2 <- 11 + c(-10:10) %o% cos(seq(-pi, pi, 2*pi/20))
# yy2 <- 11 + c(-10:10) %o% sin(seq(-pi, pi, 2*pi/20))
xx2 <- 11 + seq(0,10,1/2) %o% cos(seq(-pi, pi, 2*pi/20))
yy2 <- 11 + seq(0,10,1/2) %o% sin(seq(-pi, pi, 2*pi/20))
#zz2 <- as.vector(matrix_ON_smooth) %o% rep(1, 21*21)
zz2 <- matrix_ON_smooth
ww2 <- matrix_OFF_smooth
dd2 <- matrix_ON_smooth-matrix_OFF_smooth
#plot 2D into 3D and make the text of the diameter (time), height (r), and phase (phi)
f <- list(family = "Courier New, monospace", size = 18, color = "black")
x <- list(title = "k1", titlefont = f)
y <- list(title = "k2", titlefont = f)
z <- list(title = "fMRI Kime-series", titlefont = f)
# matrix_ON_smooth,matrix_OFF_smooth is indexed in Cartesian coordinates
# Reinterpolate the fMRI intensities according to Polar grid
library(akima)
ON_transformed <- matrix(0, nrow = 21, ncol = 21)
OFF_transformed <- matrix(0, nrow = 21, ncol = 21)
ON_OFF_transformed <- matrix(0, nrow = 21, ncol = 21)
cart_x <- as.vector(rep(seq(1,21),21))
cart_y <- as.vector(sort(rep(seq(1,21),21)))
cart_z_on <- as.vector(matrix_ON_smooth)
cart_z_diff <- as.vector(matrix_ON_smooth-matrix_OFF_smooth)
cart_z_off <- as.vector(matrix_OFF_smooth)
for(i in 1:21){
for(j in 1:21){
#interpolations
int_res_on <- interp(cart_x,cart_y,cart_z_on,xx2[i,j],yy2[i,j])
int_res_off <- interp(cart_x,cart_y,cart_z_off,xx2[i,j],yy2[i,j])
int_res_diff <- interp(cart_x,cart_y,cart_z_diff,xx2[i,j],yy2[i,j])
#insert data
ON_transformed[i,j] = as.numeric(int_res_on$z)
OFF_transformed[i,j] = as.numeric(int_res_off$z)
ON_OFF_transformed[i,j] = as.numeric(int_res_diff$z)
# if None set to 0
if(is.na(ON_transformed[i,j])){
ON_transformed[i,j] = 0
}
if(is.na(ON_OFF_transformed[i,j])){
ON_OFF_transformed[i,j] = 0
}
if(is.na(OFF_transformed[i,j])){
ON_OFF_transformed[i,j] = 0
}
}
}
# plot_ly(x = xx2,y = yy2,z = ON_transformed) %>% add_surface()
Generate the \(21\times 21\) kime-domain Cartesian grid hovering texts.
# hoverText <- cbind(x=1:21, y=1:21, height=as.vector(t(matrix_ON_smooth))) # tail(mytext)
custom_txt <- matrix(NA, nrow=21, ncol=21)
# hoverTextOFF <- cbind(x=1:21, y=1:21, height=as.vector(t(matrix_OFF_smooth))) # tail(mytext)
custom_txtOFF <- matrix(NA, nrow=21, ncol=21)
custom_txt_DIFF <- matrix(NA, nrow=21, ncol=21)
custom_txt_ON_OFF <- matrix(NA, nrow=21, ncol=21)
for (xdir in 1:21) {
for (ydir in 1:21) {
custom_txt[xdir,ydir] <- paste(' fMRI: ', round(ON_transformed[xdir,ydir], 3),
'\n time: ', round((xdir-1)/2, 0),
'\n phi: ', round(-pi+pi/10*(ydir-1), 2))
custom_txtOFF[xdir,ydir] <- paste(' fMRI: ', round(OFF_transformed[xdir,ydir], 3),
'\n time: ', round((xdir-1)/2, 0),
'\n phi: ', round(-pi+pi/10*(ydir-1), 2))
custom_txt_DIFF[xdir,ydir] <- paste(' fMRI: ', round(ON_transformed[xdir,ydir]-OFF_transformed[xdir,ydir], 3),
'\n time: ', round((xdir-1)/2, 0),
'\n phi: ', round(-pi+pi/10*(ydir-1), 2))
custom_txt_ON_OFF[xdir,ydir] <- paste(' fMRI: ', round(ON_OFF_transformed[xdir,ydir], 3),
'\n time: ', round((xdir-1)/2, 0),
'\n phi: ', round(-pi+pi/10*(ydir-1), 2))
}
}
plot_ly(x = xx2,y = yy2,z = ON_transformed, type = "surface", colors=c("#FFFFFF","#0000FF"),
text = custom_txt, hoverinfo = "text", showlegend = FALSE) %>%
add_trace(x=11, y=11, z=0:0.15, type="scatter3d", mode="lines",
line = list(width = 10, color="red"), name="Space(x)",
hoverinfo="none", showlegend = FALSE) %>%
layout(dragmode = "turntable", title = "ON kimesurface/Kime-Series at a fixed voxel location",scene = list(xaxis = x, yaxis = y, zaxis = z), showlegend = FALSE)
plot_ly(x = ~xx2, y = ~yy2, z = ~OFF_transformed, type = "surface",colors=c("#FFFFFF","#0000FF"), # scatterpolar
text = custom_txtOFF, hoverinfo = "text", showlegend = FALSE) %>%
#add_trace(x=~xx2, y=~yy2, z=~ww2, colors = c("blue", "yellow"),
# type="surface", text = custom_txtOFF, hoverinfo = "text",
# opacity=0.3, showscale = FALSE, showlegend = FALSE) %>%
# trace the main Z-axis
add_trace(x=11, y=11, z=0:0.15, type="scatter3d", mode="lines",
line = list(width = 10, color="red"), name="Space(x)",
hoverinfo="none", showlegend = FALSE) %>%
layout(dragmode = "turntable", title = "OFF kimesurface/Kime-Series at a fixed voxel location",
scene = list(xaxis = x, yaxis = y, zaxis = z), showlegend = FALSE)
# x <- as.vector(rep(seq(0.5,20.5),21))
# y <- as.vector(sort(rep(seq(0.5,20.5),21)))
# z <- as.vector(matrix_ON_smooth)
# intper <- interp(x,y,z)
# plot_ly(x = intper$x,y = intper$y,z = matrix_ON_smooth) %>% add_surface()
plot_ly(x = ~xx2, y = ~yy2, z = ~ON_transformed-OFF_transformed, type = "surface",colors=c("#FFFF00","#FFFFFF","#0000FF"), # scatterpolar
text = custom_txt_DIFF, hoverinfo = "text", showlegend = FALSE) %>%
#add_trace(x=~xx2, y=~yy2, z=~ww2, colors = c("blue", "yellow"),
# type="surface", text = custom_txtOFF, hoverinfo = "text",
# opacity=0.3, showscale = FALSE, showlegend = FALSE) %>%
# trace the main Z-axis
add_trace(x=11, y=11, z=0:0.15, type="scatter3d", mode="lines",
line = list(width = 10, color="red"), name="Space(x)",
hoverinfo="none", showlegend = FALSE) %>%
layout(dragmode = "turntable", title = "Raw ON-OFF difference kimesurface/Kime-Series at a fixed voxel location",
scene = list(xaxis = x, yaxis = y, zaxis = z), showlegend = FALSE)
#plot 2D into 3D and make the text of the diameter (time), height (r), and phase (phi)
zd <- list(title = "fMRI Kime-ON/OFF difference", titlefont = f)
dd2 <- matrix_ON_smooth-matrix_OFF_smooth
# dd2scale<-fmri_split_ab_bl(dd2)
plot_ly(x = ~xx2, y = ~yy2, z = ~ON_OFF_transformed, type = "surface", #colors = dd2scale,
colors=c("#FFFF00","#FFFFFF","#0000FF"), # scatterpolar
text = custom_txt_ON_OFF, hoverinfo = "text", showlegend = FALSE) %>%
#add_trace(x=~xx2, y=~yy2, z=~ww2, colors = c("blue", "yellow"),
# type="surface", text = custom_txtOFF, hoverinfo = "text",
# opacity=0.3, showscale = FALSE, showlegend = FALSE) %>%
# trace the main Z-axis
add_trace(x=11, y=11, z=-0.15:0.15, type="scatter3d", mode="lines",
line = list(width = 10, color="red"), name="Space(x)",
hoverinfo="none", showlegend = FALSE) %>%
layout(dragmode = "turntable", title = "Difference for ON & OFF kimesurface/Kime-Series at a fixed voxel location",
scene = list(xaxis = x, yaxis = y, zaxis = zd), showlegend = FALSE)
The last two kimesurface plots are almost identical as the interpolation process is fairly robust.
Convert the entire dataset into a long data-frame, i.e., construct a data-frame (DF) with \(\underbrace{160}_{total}=\underbrace{2}_{binary\\ On/Off}\times \underbrace{8}_{repeats}\times \underbrace{10}_{timepoints}\) rows and \(4\) columns representing time (\(1:10\)), phases (\(8\)), states (\(2\)), and fMRI_value (Complex or Real time-series intensity). Then, transform the long DF representing the fMRI_ON and fMRI_OFF time-sources from their native (old) polar coordinates to the (new) Cartesian coordinates, using polar transformations.
Use plot_ly
to display in 3D the kime-series as 2D
manifolds (kimesurfaces) over the Cartesian domain. In this specific
binary case-study, we demonstrate the following \(3\) kimesurface reconstructions:
Above we demonstrated transforming repeated sampling longitudinal processes (time-series) as 2D manifolds (kimesurfaces) using a kime-phase prior distribution, such as the truncated Laplace phase distribution.
An alternative numerical protocols utilizes autoregressive model, \(AR(p)\). For instance, here is an \(AR(1)\) kime-phase model \(\hat{\theta}_{t}=\alpha_1 \theta_{t-1}+ \varepsilon _{t}\), where \(\varepsilon_{t}\sim N(0,\sigma_{\varepsilon}^2)\) (white noise) or \(\varepsilon_{t}\sim \operatorname {Laplace}(\mu=0,scale=\sigma_{\varepsilon}^2)\) (Laplace phase), \(\theta_{t}\equiv \hat{\theta}_{t} {\text{ mod }} 2\pi \sim \Phi_{[-\pi,\pi)}(t)\), and the (time-dependent) kime-phase distribution \(\Phi_{[-\pi,\pi)}(t)\) is compactly supported over the (periodic) interval \([-\pi,\pi)\).
In the more general case, the \(AR(p)\) autoregressive model is \[{\hat{\theta}}_{t}= \sum_{i=1}^{p}\alpha_{i} \theta_{t-i} + \varepsilon _{t}, \ \ \theta_{t}\equiv \hat{\theta}_{t} {\text{ mod }} 2\pi \sim \Phi_{[-\pi,\pi)}(t)\]
where the coefficients \(\alpha_{i}, \ i\in\{1, 2, \cdots, p\}\)}} are hyper-parameters that can be set, estimated, or optimized to control the covariance function of the model. The \(AR(p)\) autoregressive model may also be expressed using the backshift operator \(B\)
\[\hat{\theta}_{t} = \sum_{i=1}^{p}\alpha_{i}B^{i}\theta_{t} + \varepsilon _{t}, \ \ \theta_{t}\equiv \hat{\theta}_{t} {\text{ mod }} 2\pi \sim \Phi_{[-\pi,\pi)}(t).\]
Rearranging the terms on both hands sides, \(\alpha [B]X_{t} = \varepsilon_{t}\), where \(\alpha(z)\equiv 1 - \sum_{i=1}^{p} \alpha_{i}z^{i}\), each \(z_{i}\in\mathbb{C}\) satisfies \(|z_{i}|>1\), and the backshift operator \(B^{i}\theta_{t}=\theta_{t-i},\ \forall\ i\in\mathbb{N}\).
Let’s run the same fMRI kimesurface reconstruction we did above (using the Laplace kime-phase prior distribution model) on the \(AR(p=1)\) kime-phase temporal dynamics \(t\in\{1,2,3,\cdots,10\}\), and visually compare the fMRI On, Off, and On-Off difference kimesurface reconstructions. As expected these kimesurafces look smoother and analytic.
These examples connect to several ongoing spacekime analytics R&D projects, including kimesurface analyticity, kimesurface regularization, and distribution-time dynamics.
# AR(1) model for generating 8 phi kime-phases for each of the 10 time radii
# phi_8_vec_AR1 <- matrix(NA, ncol=10, nrow = 8)
library(tidyr)
# Initialize all the phases: all repeats: 1<=i<=8, and times: 1<=t<=10
for (t in 1:10) {
phi_8_vec[ , t] <- extraDistr::rlaplace(8, mu=0, sigma=0.5)
}
# AR(1) Modeling for all 8 fMRI repeats; for ARIMA(p,q,r), update order=c(p,q,r)
phi_8_vec_AR1 <- list()
for (i in 1:8) {
phi_8_vec_AR1[[i]] <- arima(phi_8_vec[i , ], order=c(1,0,0))
}
# AI modeling and forcing all phases in [-pi: pi), modulo or via wrap around correction
for (i in 1:8) {
for (t in 2:10) { # fitting AR(p=1) model starts with t=1+p=2
# Use AR(1) models to adjust the phases
phi_8_vec[i, t] <- phi_8_vec_AR1[[i]]$coef[2] +
phi_8_vec_AR1[[i]]$coef[1] * (phi_8_vec[i, t-1] - phi_8_vec_AR1[[i]]$coef[2])
+ phi_8_vec_AR1[[i]]$residuals[t] ## Should the random error (residuals) be added???
# force all phases in [-pi: pi), e.g., phi_8_vec_AR1[i,t] %% pi (wrap around)
if (phi_8_vec[i,t] < -pi)
phi_8_vec[i,t] <- phi_8_vec[i,t] %% pi
if (phi_8_vec[i,t] >= pi)
phi_8_vec[i,t] <- ((phi_8_vec[i,t] + pi) %% pi) -pi
}
}
phi_8_vec_AR1_df <- as.data.frame(t(phi_8_vec))
phi_8_vec_AR1_df$time <- c(1:10)
phi_8_vec_AR1_df_long <- phi_8_vec_AR1_df %>%
pivot_longer(!time, names_to = "repeats", values_to = "values")
plot_ly(data = phi_8_vec_AR1_df_long,x = ~time, y=~values, type="scatter", mode="lines",
color = ~repeats, name= ~repeats) %>%
layout(title="AR(1) Kime-Phase Prior Model based on Laplace Noise",
legend = list(title="Repeats", orientation = 'h', y=-0.2))
In the example above, the \(AR(1)\) model for a fixed fMRI sequence repeat index \(r\) is
\[fMRI_t(r) - \mu(r) = \alpha_1(r)(fMRI_{t-1}(r) - \mu(r)) + \varepsilon_t(r)\ .\] In this experiment, for the first fMRI repeat index, \(r=1\), the estimated \(AR(p=1)\) model coefficients and residuals were:
Observation: As expected, the \(AR(p=1)\) (longitudinal) model of the kime-phases stabilizes with time. This may be important when kimesurface analyticity, smoothness, or other properties of the time-series to manifold transformations may be desirable. Let’s also illustrate the resulting On, Off, and On-Off difference kimesurface reconstructions. These (\(AR(p=1)\) regularized) kimesurfaces can be contrasted against their earlier counterparts, which used the raw Laplace kime-phase samples without \(AR(1)\) modeling.
# Convert the long DF representing fMRI_ON and fMRI_OFF from polar coordinates to Cartesian coordinates
library(spatstat)
matrix_ON <- matrix(0, nrow = 21, ncol = 21)
matrix_OFF <- matrix(0, nrow = 21, ncol = 21)
for (t in 1:10) {
for (p in 1:8) {
x = 11+t*cos(phi_8_vec[p,t])
y = 11+t*sin(phi_8_vec[p,t])
matrix_ON[x,y] <- fMRI_ON[(p-1)*10 +t] # What is matrix_ON?
matrix_OFF[x,y] <- fMRI_OFF[(p-1)*10 +t]
}
}
# smooth/blur the matrices
matrix_ON_smooth <- (1/10000)*as.matrix(blur(as.im(matrix_ON), sigma=0.5))
matrix_OFF_smooth <- (1/10000)*as.matrix(blur(as.im(matrix_OFF), sigma=0.5))
plotly
labelsGenerate the plot_ly
text labels that will be shown over
the graph, upon mouse hovering (pop-up dialogues) over each
kimesurface/kime-series. These text-labels are stored in Cartesian
coordinates \((-10\leq x\leq 10,-10\leq y\leq
10)\), but are computed using the polar coordinates \((1\leq t\leq 10,-\pi\leq \phi<\pi)\) and
the polar-to-Cartesian transformation. The labels are \(21\times 21\) arrays including the triple
\((t, \phi, fMRIvalue)\). Separate
text-labels are generated for each kimesurface (ON vs. OFF
stimuli).
Interpolate the fMRI intensities, natively anchored at polar (kime) coordinates) \(\left (\underbrace{t}_{time},\underbrace{\phi}_{repeat} \right)\), into Cartesian coordinates \(fMRI(x,y), \forall -11\leq x,y \leq 11, x^2+y^2\leq 10\). Note that for specificity, we hard-coded polar-grid parameterization to time \(t\in\{1,2,3, \cdots,10\},\ |T|=10\) and phase \(\phi=seq\left (from=-\pi, to=\pi, step=\frac{2\pi}{20}\right )\in\{-3.1415927, -2.8274334, \cdots, 0.0,\cdots, 2.8274334, 3.1415927 \},\ |\Phi|=21\).
# xx2 <- 11 + c(-10:10) %o% cos(seq(-pi, pi, 2*pi/20))
# yy2 <- 11 + c(-10:10) %o% sin(seq(-pi, pi, 2*pi/20))
xx2 <- 11 + seq(0,10,1/2) %o% cos(seq(-pi, pi, 2*pi/20))
yy2 <- 11 + seq(0,10,1/2) %o% sin(seq(-pi, pi, 2*pi/20))
#zz2 <- as.vector(matrix_ON_smooth) %o% rep(1, 21*21)
zz2 <- matrix_ON_smooth
ww2 <- matrix_OFF_smooth
dd2 <- matrix_ON_smooth-matrix_OFF_smooth
#plot 2D into 3D and make the text of the diameter (time), height (r), and phase (phi)
f <- list(family = "Courier New, monospace", size = 18, color = "black")
x <- list(title = "k1", titlefont = f)
y <- list(title = "k2", titlefont = f)
z <- list(title = "AR(1) fMRI Kime-series", titlefont = f)
# matrix_ON_smooth,matrix_OFF_smooth is indexed in Cartesian coordinates
# Reinterpolate the fMRI intensities according to Polar grid
library(akima)
ON_transformed <- matrix(0, nrow = 21, ncol = 21)
OFF_transformed <- matrix(0, nrow = 21, ncol = 21)
ON_OFF_transformed <- matrix(0, nrow = 21, ncol = 21)
cart_x <- as.vector(rep(seq(1,21),21))
cart_y <- as.vector(sort(rep(seq(1,21),21)))
cart_z_on <- as.vector(matrix_ON_smooth)
cart_z_diff <- as.vector(matrix_ON_smooth-matrix_OFF_smooth)
cart_z_off <- as.vector(matrix_OFF_smooth)
for(i in 1:21){
for(j in 1:21){
#interpolations
int_res_on <- interp(cart_x,cart_y,cart_z_on,xx2[i,j],yy2[i,j])
int_res_off <- interp(cart_x,cart_y,cart_z_off,xx2[i,j],yy2[i,j])
int_res_diff <- interp(cart_x,cart_y,cart_z_diff,xx2[i,j],yy2[i,j])
#insert data
ON_transformed[i,j] = as.numeric(int_res_on$z)
OFF_transformed[i,j] = as.numeric(int_res_off$z)
ON_OFF_transformed[i,j] = as.numeric(int_res_diff$z)
# if None set to 0
if(is.na(ON_transformed[i,j])){
ON_transformed[i,j] = 0
}
if(is.na(ON_OFF_transformed[i,j])){
ON_OFF_transformed[i,j] = 0
}
if(is.na(OFF_transformed[i,j])){
ON_OFF_transformed[i,j] = 0
}
}
}
# plot_ly(x = xx2,y = yy2,z = ON_transformed) %>% add_surface()
Generate the \(21\times 21\) kime-domain Cartesian grid hovering texts.
# hoverText <- cbind(x=1:21, y=1:21, height=as.vector(t(matrix_ON_smooth))) # tail(mytext)
custom_txt <- matrix(NA, nrow=21, ncol=21)
# hoverTextOFF <- cbind(x=1:21, y=1:21, height=as.vector(t(matrix_OFF_smooth))) # tail(mytext)
custom_txtOFF <- matrix(NA, nrow=21, ncol=21)
custom_txt_DIFF <- matrix(NA, nrow=21, ncol=21)
custom_txt_ON_OFF <- matrix(NA, nrow=21, ncol=21)
for (xdir in 1:21) {
for (ydir in 1:21) {
custom_txt[xdir,ydir] <- paste(' AR(p=1) fMRI: ', round(ON_transformed[xdir,ydir], 3),
'\n time: ', round((xdir-1)/2, 0),
'\n phi: ', round(-pi+pi/10*(ydir-1), 2))
custom_txtOFF[xdir,ydir] <- paste(' fMRI: ', round(OFF_transformed[xdir,ydir], 3),
'\n time: ', round((xdir-1)/2, 0),
'\n phi: ', round(-pi+pi/10*(ydir-1), 2))
custom_txt_DIFF[xdir,ydir] <- paste(' fMRI: ', round(ON_transformed[xdir,ydir]-OFF_transformed[xdir,ydir], 3),
'\n time: ', round((xdir-1)/2, 0),
'\n phi: ', round(-pi+pi/10*(ydir-1), 2))
custom_txt_ON_OFF[xdir,ydir] <- paste(' fMRI: ', round(ON_OFF_transformed[xdir,ydir], 3),
'\n time: ', round((xdir-1)/2, 0),
'\n phi: ', round(-pi+pi/10*(ydir-1), 2))
}
}
plot_ly(x = xx2,y = yy2,z = ON_transformed, type = "surface", colors=c("#FFFFFF","#0000FF"),
text = custom_txt, hoverinfo = "text", showlegend = FALSE) %>%
add_trace(x=11, y=11, z=0:0.15, type="scatter3d", mode="lines",
line = list(width = 10, color="red"), name="Space(x)",
hoverinfo="none", showlegend = FALSE) %>%
layout(dragmode="turntable",
title="Kime-Phase AR(1) Model:\n ON kimesurface at a fixed voxel location",
scene=list(xaxis=x, yaxis = y, zaxis = z)) %>% hide_colorbar()
plot_ly(x = ~xx2, y = ~yy2, z = ~OFF_transformed, type = "surface",colors=c("#FFFFFF","#0000FF"), # scatterpolar
text = custom_txtOFF, hoverinfo = "text", showlegend = FALSE) %>%
#add_trace(x=~xx2, y=~yy2, z=~ww2, colors = c("blue", "yellow"),
# type="surface", text = custom_txtOFF, hoverinfo = "text",
# opacity=0.3, showscale = FALSE, showlegend = FALSE) %>%
# trace the main Z-axis
add_trace(x=11, y=11, z=0:0.15, type="scatter3d", mode="lines",
line = list(width = 10, color="red"), name="Space(x)",
hoverinfo="none", showlegend = FALSE) %>%
layout(dragmode = "turntable",
title = "Kime-Phase AR(1) Model: OFF kimesurface at a fixed voxel location",
scene = list(xaxis = x, yaxis = y, zaxis = z)) %>% hide_colorbar()
plot_ly(x = ~xx2, y = ~yy2, z = ~ON_transformed-OFF_transformed, type = "surface",colors=c("#FFFF00","#FFFFFF","#0000FF"), # scatterpolar
text = custom_txt_DIFF, hoverinfo = "text", showlegend = FALSE) %>%
#add_trace(x=~xx2, y=~yy2, z=~ww2, colors = c("blue", "yellow"),
# type="surface", text = custom_txtOFF, hoverinfo = "text",
# opacity=0.3, showscale = FALSE, showlegend = FALSE) %>%
# trace the main Z-axis
add_trace(x=11, y=11, z=0:0.15, type="scatter3d", mode="lines",
line = list(width = 10, color="red"), name="Space(x)",
hoverinfo="none", showlegend = FALSE) %>%
layout(dragmode = "turntable",
title = "Kime-Phase AR(1) Model:\n Raw ON-OFF difference kimesurface at a fixed voxel location",
scene = list(xaxis = x, yaxis = y, zaxis = z)) %>% hide_colorbar()
#plot 2D into 3D and make the text of the diameter (time), height (r), and phase (phi)
zd <- list(title = "AR(1) fMRI Kime-ON/OFF difference", titlefont = f)
dd2 <- matrix_ON_smooth-matrix_OFF_smooth
# dd2scale<-fmri_split_ab_bl(dd2)
plot_ly(x = ~xx2, y = ~yy2, z = ~ON_OFF_transformed, type = "surface", #colors = dd2scale,
colors=c("#FFFF00","#FFFFFF","#0000FF"), # scatterpolar
text = custom_txt_ON_OFF, hoverinfo = "text", showlegend = FALSE) %>%
#add_trace(x=~xx2, y=~yy2, z=~ww2, colors = c("blue", "yellow"),
# type="surface", text = custom_txtOFF, hoverinfo = "text",
# opacity=0.3, showscale = FALSE, showlegend = FALSE) %>%
# trace the main Z-axis
add_trace(x=11, y=11, z=-0.15:0.15, type="scatter3d", mode="lines",
line = list(width = 10, color="red"), name="Space(x)",
hoverinfo="none", showlegend = FALSE) %>%
layout(dragmode = "turntable",
title = "Kime-Phase AR(1) Model:\n Difference for ON & OFF kimesurfaces at a fixed voxel location",
scene = list(xaxis = x, yaxis = y, zaxis = zd)) %>% hide_colorbar()
First plot the simulated On (stimulus) and Off (rest) fMRI time-series at a fixed voxel location \((44,42,33)\in\mathbb{R}^3\), along with the averaged (pooled) On and Off signal over all \(8\) repeats in the single run (epoch) of \(160\) time-points.
# Extract just the On kime-series at voxel (44,42,33), each time-series has 8 repeats!
onFMRISeries <-
bigim1_mod[44,42,33, c(1:10, 21:30, 41:50, 61:70, 81:90, 101:110, 121:130, 141:150)]
# the corresponding Off kime-series at voxel (44,42,33) will be the temporal complement
offFMRISeries <-
bigim1_mod[44,42,33, -c(1:10, 21:30, 41:50, 61:70, 81:90, 101:110, 121:130, 141:150)]
t_indx <- seq(1, 80, 1) # index of time (not actual time value)
f_On <- onFMRISeries
f_Off <- offFMRISeries
vline <- function(x=0, color = "lightgray") {
list(type="line", y0=0, y1=1, yref="paper", name="time break points",
opacity = 0.5, x0=x, x1=x, line=list(color=color))
}
hline <- function(y = 0, color = "blue") {
list(type="line", x0=0, x1=1, xref="paper", name="intensity break points",
opacity = 0.3, y0=y, y1=y, line=list(color=color))
}
plot_ly() %>%
# On fMRI time-series
add_trace(x=t_indx, y=f_On, type="scatter", mode="lines", name="On time-series") %>%
# On fMRI time-series
add_trace(x=t_indx, y=f_Off, type="scatter", mode="lines", name="Off time-series") %>%
# Repeated measurement break points
# add_trace(x=c(10,20,30,40,50,60,70,80), y=f_Off, type="scatter", mode="lines", name="Off time-series") %>%
layout(title="3D fMRI Simulation: On & Off Time-series at Voxel(44,42,33)",
shapes = list(
vline(10),vline(20),vline(30),vline(40),vline(50),vline(60),vline(70),vline(80)),
legend = list(orientation='h', y=-0.2))
# Compute and plot against each other the Average On and Average Off time-series
seriesAvg <- function(f=0, period=0) {
tempAvg <- rep(0,period) # initialize avg signal
for (i in c(1:period)) {
counter =0 # empirically count the number of repeated samples (here it's 8)
for (j in c(1:length(f))) {
if (j %% period == i-1) { # e.g., 159 %% 10 # [1] 9
tempAvg[i] = tempAvg[i] + f[j]
counter = counter + 1
}
}
tempAvg[i] = tempAvg[i]/counter
}
return(tempAvg)
}
period <- 10
onAvg <- seriesAvg(f=f_On, period=period)
offAvg <- seriesAvg(f=f_Off, period=period)
plot_ly() %>%
# Average On fMRI time-series
add_trace(x=c(1:period), y=onAvg, type="scatter", mode="lines", name="On Average") %>%
# On fMRI time-series
add_trace(x=c(1:period), y=offAvg, type="scatter", mode="lines", name="Off Average") %>%
# Repeated measurement break points
layout(title=
"3D fMRI Simulation: On & Off Time-series\n (averaged over all 8 repeats) at Voxel (44,42,33)",
legend = list(orientation='h', y=-0.2))
Next, we’ll define and apply the Laplace Transform (LT) and its inverse (ILT) and use them to show the analytical kimesurface reconstruction.
# create the LT
NuLT = function(datax, datay, inputz, k = 3, fitwarning = FALSE,
mirror = FALSE, range = 2*pi) {
datax = as.numeric(datax)
datay = as.numeric(datay)
n = length(datax)
x1 = n/(n+0.5)*((datax-min(datax))/(max(datax)-min(datax)))*range
if(mirror){
x1 = c(x1,rev(2*range - x1))/2
n = 2*n
datay = c(datay, rev(datay))
plot(x1, datay)
}
#generate the coefficients in indefinite integral of t^n*exp(-zt)
coef = 1;
coefm = as.matrix(coef)
for(i in 1:k){
coefm = cbind(coefm,0)
coef = c(coef*i,1)
coefm = rbind(coefm,coef)
}
# these coefficients ordered by ^0, ^1, ^2, ... in column format
# compute 1, z, z^2...,z^k
zz = cbind(1,inputz)
zt = inputz
for (i in 2:k){
zt = zt*inputz
zz = cbind(zz,zt)
}
zd = zt*inputz
# compute 1, x, x^2...,x^k
tx = x1;
xm = cbind(1,x1)
for (i in 2:k){
tx = tx*x1
xm = cbind(xm,tx)
}
# sum over intervals
result = 0*inputz
ii = 1
while(ii+k <= n) {
A = xm[seq(ii,ii+k),c(0:k+1)]
b = datay[seq(ii,ii+k)]
# polyfit might be faster when using polynomial basis, while matrix inverse, `solve()`,
# is the more general approach that works for any function basis
polyc = as.numeric(solve(A,b))
#ordered by ^0, ^1, ^2, ... in column format
# Enter a new function variable qualityCheck=FALSE
# check fit quality; this step can be skipped for speed/efficiency
# if (qualityCheck) { .... }
if (fitwarning){
xx = seq(A[1,2],A[k+1,2],length.out = 100);
yy = polyval(rev(polyc),xx)
if(max(abs(yy-mean(b)))>2*max(abs(b-mean(b)))){
print(c("Warning: Poor Fit at ",ii,", Largest Deviation is",
max(abs(yy-mean(b)))))
print(c("Spline Polynomial is", polyc),3);
#print(c(polyval(rev(polyc),A[,2]),b))
plot(xx, yy, main="Polynomial fit", ylab="", type="l", col="blue")
lines(A[,2],b, col="red")
legend("topleft",c("fit","data"),fill=c("blue","red"))
print(" ")
}
}
# Use vector/matrix operations to avoid looping,
# some of the expressions look weird
# May need to actually compare the efficiency/speed of
# vector based vs. standard numeric calculations
m1 = t(t(polyc*coefm)*A[1,])
m11 = as.numeric(tapply(m1, col(m1)-row(m1), sum))[0:k+1]
m2 = t(t(polyc*coefm)*A[k+1,])
m22 = as.numeric(tapply(m2, col(m2)-row(m2), sum))[0:k+1]
intgl = (exp(-inputz*A[1,2])*colSums(t(zz)*m11)-
exp(-inputz*A[k+1,2])*colSums(t(zz)*m22))/zd
result = result+intgl
ii=ii+k
}
# Computations over the last interval
if(ii < n){
nk = n-ii;
A = xm[seq(ii,ii+nk),c(0:nk+1)]
b = datay[seq(ii,ii+nk)]
nc = as.numeric(solve(A,b))
nc = c(nc,seq(0,0,length.out = k-nk))
A = xm[seq(ii,ii+nk),]
m1 = t(t(nc*coefm)*A[1,])
m11 = as.numeric(tapply(m1, col(m1)-row(m1), sum))[0:k+1]
m2 = t(t(nc*coefm)*A[nk+1,])
m22 = as.numeric(tapply(m2, col(m2)-row(m2), sum))[0:k+1]
# cc = colSums(coefm*polyc)
intgl = (exp(-inputz*A[1,2])*colSums(t(zz)*m11)-
exp(-inputz*A[nk+1,2])*colSums(t(zz)*m22))/zd
result = result+intgl
}
#offset = 0.05*pi
#result = result+datay[n]*(exp(-2*pi*inputz)-exp(-(2*pi+offset)*inputz))/inputz
return(result)
}
Let’s test the discrete LT using the \(\sin(x),\cos(x)\) function.
datax = seq(1,200)
n = length(datax)
x1 = n/(n+0.5)*((datax-min(datax))/(max(datax)-min(datax)))*(2*pi)
datay = cos(x1) # test function!!!
Lout = 61
range_limit <- 20
x2 <- seq(from = 0.1, to = range_limit, length.out = Lout)
y2 <- seq(from = 0, to = range_limit, length.out = Lout)
x2_g = x2 %o% seq(1,1, length.out = Lout)
y2_g = seq(1,1, length.out = Lout)%o%y2
z2_grid = x2 %o% y2
argz = as.vector(x2_g + 1i*y2_g)
LTz = NuLT(x1, datay, argz)
rec1 = matrix(LTz,nrow = Lout)
recm = abs(rec1)
recr = Re(rec1)
reci = Im(rec1)
ph = reci/recm
surf_color <- atan2(reci,recr)
# colorscale = cbind(seq(-pi, pi, by=1/10, rainbow(11)))
p <- plot_ly(hoverinfo="none", showscale = TRUE) %>%
add_trace(x = x2_g, y = y2_g, z = surf_color,
# F-magnitude or Re(F), # z = Im(z2_grid), # Real or Imaginary part of F(t)
surfacecolor=log(recm), # colorscale=colorscale, #Phase-based color
type = 'surface', opacity=1, visible=T) %>%
layout(title =
"Truncated Laplace transform of cos(x), Color = Mag(Z)",
showlegend = TRUE,
scene = list(aspectmode = "manual", aspectratio = list(x=1, y=1, z=1.0),
xaxis=list(title= "Real=Re(z)"), yaxis=list(title = "Imaginary=Im(z)"),
zaxis=list(title= "Z = tan-1(Im(L[f](z))/Re(L[f](z))))")) ) # 1:1:1 aspect ratio
p
Compare the above numerical calculation of the Laplace Transform (LT) of \(f(x)=\cos(x)\) against the below kimesurface of the exact analytical (ground truth integration) of the LT of \(f(x)=\cos(x)\), which is
\[F(z)\equiv \mathcal{L}(f)(z)\equiv \int_0^{2\pi}f(t)e^{-zt}dt = \frac{z-e^{-2\pi z}z}{1+z^2}\ .\]
z_val <- (x2_g+y2_g*1i)
ground_truth <- (z_val - exp(-2*pi*z_val)*z_val)/(1+z_val^2)
surf_ground_truth <- atan2(Im(ground_truth),Re(ground_truth))
plot_ly(hoverinfo="none", showscale = TRUE) %>%add_trace(x = x2_g, y = y2_g, z = surf_ground_truth,surfacecolor=log(abs(ground_truth)),type = 'surface', opacity=1, visible=T)%>%layout(title =
"Ground Truth for the truncated Laplace transform of cos(x), Color = Mag(Z)",scene = list(aspectmode = "manual", aspectratio = list(x=1, y=1, z=1.0),
xaxis=list(title= "Real=Re(z)"), yaxis=list(title = "Imaginary=Im(z)"),
zaxis=list(title= "Z = tan-1(Im(L[f](z))/Re(L[f](z)))")) )
The visual agreement between the exact and numerical calculations of the kimesurface \(F(z)\) corresponding to the signal \(f(x)=\cos(x)\) can also be checked by computing the average surface difference as shown below.
print(paste0("The average difference between the ground truth and the numerical integration is ",
sum(abs(ground_truth -rec1))/length(ground_truth)))
## [1] "The average difference between the ground truth and the numerical integration is 0.000156495820765476"
# datax = seq(1,200)
#
# n = length(datax)
#
# x1 = n/(n+0.5)*((datax-min(datax))/(max(datax)-min(datax)))*(2*pi)
#
# datay = sin(x1) # test function!!!
#
# Lout = 61
# range_limit <- 20
# x2 <- seq(from = 0.1, to = range_limit, length.out = Lout)
# y2 <- seq(from = 0, to = range_limit, length.out = Lout)
#
# x2_g = x2 %o% seq(1,1, length.out = Lout)
# y2_g = seq(1,1, length.out = Lout)%o%y2
# z2_grid = x2 %o% y2
#
# argz = as.vector(x2_g + 1i*y2_g)
# LTz = NuLT(x1, datay, argz)
# rec1 = matrix(LTz,nrow = Lout)
#
# recm = abs(rec1)
# recr = Re(rec1)
# reci = Im(rec1)
# ph = reci/recm
# surf_color <- atan2(reci,recr)
# # colorscale = cbind(seq(-pi, pi, by=1/10, rainbow(11)))
# p <- plot_ly(hoverinfo="none", showscale = TRUE) %>%
# add_trace(x = x2_g, y = y2_g, z = surf_color,
# # F-magnitude or Re(F), # z = Im(z2_grid), # Real or Imaginary part of F(t)
# surfacecolor=log(recm), # colorscale=colorscale, #Phase-based color
# type = 'surface', opacity=1, visible=T) %>%
# layout(title =
# "Laplace transform of the sin(x), Color = log(mag)",
# showlegend = TRUE,
# scene = list(aspectmode = "manual", aspectratio = list(x=1, y=1, z=1.0),
# xaxis=list(title= "Real=Re(z)"), yaxis=list(title = "Imaginary=Im(z)"),
# zaxis=list(title= "Z = tan-1(Im(L[f](z))/Re(L[f](z)))")) ) # 1:1:1 aspect ratio
# p
# z_val <- (x2_g+y2_g*1i)
# ground_truth <- (1 - exp(-2*pi*z_val))/(1+z_val^2)
# surf_ground_truth <- atan2(Im(ground_truth),Re(ground_truth))
# plot_ly(hoverinfo="none", showscale = TRUE) %>%add_trace(x = x2_g, y = y2_g, z = surf_ground_truth,surfacecolor=log(abs(ground_truth)),type = 'surface', opacity=1, visible=T)%>%layout(title =
# "Ground Truth for the truncated Laplace transform of sin(x), Color = Mag(Z)",scene = list(aspectmode = "manual", aspectratio = list(x=1, y=1, z=1.0),
# xaxis=list(title= "Real=Re(z)"), yaxis=list(title = "Imaginary=Im(z)"),
# zaxis=list(title= "Z = tan-1(Im(L[f](z))/Re(L[f](z)))")) )
Next, apply the discrete LT to the average-On
(onAvg
) and average-Off signals
(offAvg
), interpolating from their original size, \(n=10\), to a new supersampled size
\(n=200\), and transforming the time
support from \(t\in[1:10]\) in
increments of \(\Delta t=1\),
to \(t' \in[0,2\pi)\), in
increments of \(\Delta
t'=\frac{n}{n+0.5}\times \frac{1}{(200-1)2\pi}.\)
This numerical longitudinal data preprocessing is done purely to establish some homologies in the structure of the LT domain, i.e., the input space signals (time-series), and the LT Range, i.e., the output space manifold (kimesurface). See the DSPA2 signal interpolation appendix to find out how to regularize either regularly (equally-spaced) sampled longitudinal data or irregularly (unequally-spaced) sampled longitudinal data.
datax = seq(1,200)
n = length(datax)
x_old <- c(1:10)
x1 = n/(n+0.5)*((datax-min(datax))/(max(datax)-min(datax)))*(2*pi)
# longitudinal data series are: onAvg & offAvg
xnew <- x1
spl_onAvg <- spline(x=x_old, y=onAvg, xmin=min(x_old), xmax=max(x_old), n=200)
spl_offAvg <- spline(x=x_old, y=offAvg, xmin=min(x_old), xmax=max(x_old), n=200)
plot_ly(type="scatter", mode="markers") %>%
add_trace(x=x_old, y=onAvg, name="Raw ON-Avg",
marker=list(size=20, color="lightblue", sizemode="area")) %>%
add_trace(x=x_old, y=offAvg, name="Raw OFF-Avg",
marker=list(size=20, color="orange", sizemode="area")) %>%
add_trace(x=spl_onAvg$x, y=spl_onAvg$y, type="scatter", mode="markers+lines",
name="Spline ON-Avg Model", marker=list(size=8, color="blue"),
line = list(color = "blue", width = 4)) %>%
add_trace(x=spl_offAvg$x, y=spl_offAvg$y, type="scatter", mode="markers+lines",
name="Spline OFF-Avg Model", marker=list(size=8, color="red"),
line = list(color = "red", width = 4)) %>%
layout(title="Spline Modeling of 1D ON and OFF fMRI data\n (averaged over repeated samples)",
legend = list(orientation = 'h', y=-0.2)) %>%
hide_colorbar()
# datay = sin(x1) # test function!!!
# Define a time-series to kimesurface plotting function
plotKimeSurface <- function(datay = sin(x1),index="",
title="Laplace transform of the Time-Series, Color = phase(Z)") {
Lout = 61
range_limit <- 20
x2 <- seq(from = 0.1, to = range_limit, length.out = Lout)
y2 <- seq(from = 0, to = range_limit, length.out = Lout)
x2_g = x2 %o% seq(1,1, length.out = Lout)
y2_g = seq(1,1, length.out = Lout)%o%y2
# z2_grid = x2 %o% y2
argz = as.vector(x2_g + 1i*y2_g)
# print(argz)
LTz = NuLT(x1, datay, argz)
rec1 = matrix(LTz,nrow = Lout)
recm = abs(rec1)
recr = Re(rec1)
reci = Im(rec1)
ph = reci/recm
surf_color <- atan2(reci,recr)
# colorscale = cbind(seq(-pi, pi, by=1/10, rainbow(11)))
p <- plot_ly(hoverinfo="none", showscale = TRUE,
scene = paste("scene",as.character(index),sep = "")) %>%
add_trace(x = x2_g, y = y2_g, z = surf_color,
# F-magnitude or Re(F), # z=Im(z2_grid), # Real or Imaginary part of F(t)
surfacecolor=log(recm), # colorscale=colorscale, #Phase-based color
type = 'surface', opacity=1, visible=T) %>%
layout(title = title, showlegend = TRUE,scene=list(aspectmode="manual", aspectratio=list(x=1, y=1, z=1.0),
xaxis=list(title= "Real"), yaxis=list(title = "Imaginary"),
zaxis=list(title= "Z = phase")) ) # 1:1:1 aspect ratio)
return(list("plot"=p,"phase"=surf_color,"magnitude"=recm,"x_grid"=x2_g,"y_grid"=y2_g))
}
#paste("scene",as.character(index),sep = "")
# list(aspectmode="manual", aspectratio=list(x=1, y=1, z=1.0),
# xaxis=list(title= "Real"), yaxis=list(title = "Imaginary"),
# zaxis=list(title= "Z = log(mag(Lf(x+iy)))")) ) # 1:1:1 aspect ratio
pOn <- plotKimeSurface(datay=spl_onAvg$y, "",
title="ON fMRI ((44,42,33): Laplace transform of Avg Time-Series, Color = phase(Z)")
pOff <- plotKimeSurface(datay=spl_offAvg$y,2,
title="OFF fMRI ((44,42,33): Laplace transform of Avg Time-Series, Color = phase(Z)")
# combineWidgets(pOn, pOff)
# synch both 3D scenes in 1 3D plot
# install.packages("manipulateWidget")
# subplot(pOn, pOff, nrows = 1, shareX = TRUE)
# main_plot <- subplot(pOn, pOff, nrows = 2, shareX = TRUE, margin = 0.06) %>%
# layout(title = "Kimesurfaces On (activation) and Off (rest) fMRI voxel (44,42,33)",
# scene = list(domain = list(x = c(0, 0.5), y = c(0.5, 1)), aspectmode = "cube"),
# scene2 = list(domain = list(x = c(0.2, 0.7), y = c(0.5, 1)), aspectmode = "cube"))
#
# main_plot %>%
# htmlwidgets::onRender(
# "function(x, el) {
# x.on('plotly_relayout', function(d) {
# const camera = Object.keys(d).filter((key) => /\\.camera$/.test(key));
# if (camera.length) {
# const scenes = Object.keys(x.layout).filter((key) => /^scene\\d*/.test(key));
# const new_layout = {};
# scenes.forEach(key => {
# new_layout[key] = {...x.layout[key], camera: {...d[camera]}};
# });
# Plotly.relayout(x, new_layout);
# }
# });
# }")
library(plotly)
library(htmlwidgets)
# Generate some example 3D data
# ... (Create your pOn and pOff plots here)
diff_plot <- plot_ly(hoverinfo="none", showscale = TRUE,
scene = "scene3") %>%
add_trace(x = pOn$x_grid, y = pOn$y_grid, z = pOn$phase - pOff$phase,
# F-magnitude or Re(F), # z=Im(z2_grid), # Real or Imaginary part of F(t)
surfacecolor=pOn$magnitude-pOff$magnitude, # colorscale=colorscale, #Phase-based color
type = 'surface', opacity=1, visible=T) %>%
layout(title = title, showlegend = TRUE,scene3=list(aspectmode="manual", aspectratio=list(x=1, y=1, z=1.0),
xaxis=list(title= "Real"), yaxis=list(title = "Imaginary"),
zaxis=list(title= "Z = Phase")) )
# Combine them into a subplot
main_plot <- subplot(pOn$plot, pOff$plot,diff_plot) %>%
layout(
title = "kimesurfaces (Left: On; Right: Off; Bottom: Kimesurface Difference (On vs. Off))",
scene = list(domain = list(x = c(0, 0.5), y = c(0.5, 1)), aspectmode = "cube"),
scene2 = list(domain = list(x = c(0.5, 1), y = c(0.5, 1)), aspectmode = "cube"),
scene3 = list(domain = list(x = c(0.25, 0.75), y = c(0, 0.5)), aspectmode = "cube")
)
#https://stackoverflow.com/questions/77757102/possible-to-synchronize-3d-scatter-plotly-plots-in-r
main_plot %>%
htmlwidgets::onRender(
"function(x, el) {
x.on('plotly_relayout', function(d) {
const camera = Object.keys(d).filter((key) => /\\.camera$/.test(key));
if (camera.length) {
const scenes = Object.keys(x.layout).filter((key) => /^scene\\d*/.test(key));
const new_layout = {};
scenes.forEach(key => {
new_layout[key] = {...x.layout[key], camera: {...d[camera]}};
});
Plotly.relayout(x, new_layout);
}
});
}")
Next, we will explore alternative strategies to map repeated measurement time-series into kimesurfaces. These approaches attempt to resolve the issues with mapping the highly irregular (non-analytic/non-holomorphic) kimesurfaces to more locally smooth analytic functions.
Protocol:
Integral transforms are mathematical operators maping functions from one domain to another, often simplifying the analysis of problems by transforming differential equations into algebraic equations or by converting convolutions into products. Each transform has specific properties, applications, and interpretations, making them essential tools in areas like signal processing, physics, and engineering. Below are commontly used integral transforms, along with their interrelations and interpretations of their forward and reverse transforms.
The Fourier Transform, one of the most widely used integral transforms, decomposes a function into its constituent frequencies, transforming a time-domain signal into the frequency domain (k-space). For a continuous function \(f(t)\), the Fourier transform \(\mathcal{F}\{f(t)\} = F(\omega)\) is defined by \(F(\omega) = \int_{-\infty}^{\infty} f(t) e^{-i\omega t} \, dt .\)
The inverse Fourier transform recovers \(f(t)\) from \(F(\omega)\) using \(f(t) = \frac{1}{2\pi} \int_{-\infty}^{\infty} F(\omega) e^{i\omega t} \, d\omega .\)
The forward Fourier Transform (FT) maps a time-domain signal into the frequency domain, where \(F(\omega)\) represents the amplitude and phase of the frequencies present in the original signal. Whereas the inverse FT reconstructs the time-domain representation of the signal by summing over all frequency components weighted by their amplitudes and phases.
The Laplace Transform (LT) is closely related to the Fourier Transform but is typically used for functions defined on \([0, \infty)\), i.e., time-series. It is particularly useful in solving linear differential equations and control theory problems. For a function of time, \(f(t)\), the Laplace transform \(\mathcal{L}\{f(t)\} = F(s)\) is defined by \(F(s) = \int_0^{\infty} f(t) e^{-st} \, dt .\)
Then, the inverse Laplace transform, used to recover \(f(t)\) from \(F(s)\), is \(f(t) = \frac{1}{2\pi i} \int_{\gamma - i\infty}^{\gamma + i\infty} F(s) e^{st} \, ds .\) The forward LT sends a time-domain function into the complex \(\mathbb{C}\)-domain, capturing both growth and oscillatory behavior. The inverse LT similarly recovers the time-domain function from its \(z\)-domain representation, often involving complex analysis techniques like contour integration.
The Hilbert Transform (HT) is an integral transform that provides a phase shift to each frequency component of a signal. It is particularly useful in signal processing for creating analytic signals and in the study of harmonic analysis. For a function \(f(t)\), the Hilbert transform \(\mathcal{H}\{f(t)\} = \tilde{f}(t)\) is defined by \(\tilde{f}(t) = \frac{1}{\pi} \text{P.V.} \int_{-\infty}^{\infty} \frac{f(\tau)}{t - \tau} \, d\tau ,\) where P.V. denotes the Cauchy principal value of the integral.
The HT produces a signal where each frequency component of the original signal is phase-shifted by \(90^\circ\). The combination of the original signal and its Hilbert transform creates an analytic signal (complex signal with only positive frequency components). Applying the Hilbert transform twice to a function \(u()\) yields \(\operatorname {H} {\bigl (}\operatorname {H} (u){\bigr )}(t)=-u(t)\), assuming the integrals defining both iterations converge in a suitable sense. Hence, the inverse HT is \(H^{(-1)}=-\operatorname {H}\). This links the Hilbert transform of \(u(t)\) to the Fourier transform of \(u(t)\). The Hilbert transform is an operator \[\overbrace{\mathcal {F}}^{FT}{\bigl (}\overbrace{\operatorname {H}}^{HT} (u){\bigr )}(\omega )=-i\operatorname {sgn}(\omega )\cdot {\overbrace{\mathcal {F}}^{FT}}(u)(\omega ).\]
As \(\operatorname {sgn} = \operatorname {sgn}(2\pi x)\), this relations applies to the three common FT definitions. The HT is typically applied to real-valued signals to extract the imaginary part of an analytic signal.
The Z-transform is the discrete counterpart of the Laplace Transform, typically used for discrete-time signals. It is extensively used in digital signal processing and control theory. For a discrete sequence \(f[n]\), the forward Z-transform \(\mathcal{Z}\{f[n]\} = F(z)\) is defined by \(F(z) = \sum_{n=-\infty}^{\infty} f[n] z^{-n} .\) The inverse Z-transform is \(f[n] = \frac{1}{2\pi i} \oint F(z) z^{n-1} \, dz .\)
Forward ZT converts a discrete-time sequence into a complex frequency domain where \(z\in\athbb{C}\), where as the inverse ZT recovers the original discrete-time sequence from its \(z\)-domain representation, typically involving residue calculus or partial fraction expansion.
The Mellin Transform (MT) is another integral transform often used in problems involving scaling, which connects multiplicative convolutions with additive convolutions. For a function \(f(x)\), the Mellin transform \(\mathcal{M}\{f(x)\} = F(s)\) is \(F(s) = \int_0^{\infty} x^{s-1} f(x) \, dx .\) The inverse MT is \(f(x) = \frac{1}{2\pi i} \int_{c - i\infty}^{c + i\infty} F(s) x^{-s} \, ds .\) The forward MT converts a function into the Mellin domain, which is useful when analyzing problems that exhibit scaling properties (e.g., in image processing or fractal analysis). Whereas the inverse MT reconstructs the function from its Mellin transform, often using contour integration techniques.
The Fourier transform is a special case of the Laplace transform where the real part of \(z\) (in the Laplace domain) is set to zero, i.e., \(z = i\omega\). Thus, the Laplace Transform generalizes the Fourier Transform by considering exponential growth/decay in addition to oscillations. Similarly, the Hilbert transform is closely related to the Fourier transform, as it can be viewed as a frequency-domain filter that shifts the phase of the positive frequencies by \(-90^\circ\) and the negative frequencies by \(+90^\circ\). This makes the Hilbert transform useful for analytic signal generation. The Z-transform can be viewed as a discrete-time version of the Laplace transform, just as the Discrete-Time Fourier transform (DTFT) is the discrete counterpart of the continuous Fourier Transform. When \(z = e^{i\omega}\), the Z-transform becomes the DTFT.
Finally, the Mellin transform is related to the Fourier Transform by a logarithmic scaling of the input variable. If a function’s domain is logarithmically transformed, the Mellin transform becomes a Fourier transform in the logarithmic domain.
Protocol:
library(spatstat)
library(plotly)
library(htmlwidgets)
# Extract just the On kime-series at voxel (44,42,33), each time-series includes 10 time-points and has 8 repeats!
# onFMRISeries <- bigim1_mod[44,42,33, c(1:10, 21:30, 41:50, 61:70,
# 81:90, 101:110, 121:130, 141:150)]
# # the corresponding Off kime-series at voxel (44,42,33) will be the temporal complement
# offFMRISeries <- bigim1_mod[44,42,33, -c(1:10, 21:30, 41:50, 61:70,
# 81:90, 101:110, 121:130, 141:150)]
# t_indx <- seq(1, 80, 1) # index of time (not actual time value)
# f_On <- onFMRISeries
# f_Off <- offFMRISeries
# Compute and plot against each other the Average On and Average Off time-series
period <- 10 # repeated measurement period
numSeries <- 8 # number of fMRI timeseries in the entire run (for each of On and Off signals)
extract8fMRI_TimeSeries <- function(f=0, period=0) {
# Split the fMRI vector into (numSeries=8) individual time-courses
fMRI_splitSeries <- split(f, ceiling(seq_along(f) / period))
return(fMRI_splitSeries)
}
# 1. Extract numSeries=8 series for stitching and kimesurface reconstruction
fMRI_ON_Series <- extract8fMRI_TimeSeries(f=f_On, period=10)
fMRI_OFF_Series <- extract8fMRI_TimeSeries(f=f_Off, period=10)
# 1.a. Stitch together (using random kime-phase draws, $\varphi\sim\Phi_{[-\pi,\pi)}$) all these complex-valued FT series into a kimesurface in k-space.
phi_8_vec <- matrix(NA, ncol=period, nrow = numSeries)
for (t in 1:period) {
# for a given t, generate 8 new phases
set.seed(t);
phi_8_vec[ ,t] <- extraDistr::rlaplace(numSeries, mu=0, sigma=0.5)
# rank-order the phases for consistency
# within the same foliation leaf
phi_8_vec[ ,t] <- sort(phi_8_vec[ ,t])
# force phases in [-pi: pi)
for (i in 1:numSeries) {
if (phi_8_vec[i,t] < -pi) phi_8_vec[i,t] <- -pi
if (phi_8_vec[i,t] >= pi) phi_8_vec[i,t] <- pi
}
}
matrix_ON <- matrix(0, nrow = 2*period+1, ncol = 2*period+1)
matrix_OFF <- matrix(0, nrow = 2*period+1, ncol = 2*period+1)
for (t in 1:period) { # POLAR to CARTESIAN coordinates
for (p in 1:numSeries) {
x = (period+1) + t*cos(phi_8_vec[p,t])
y = (period+1) + t*sin(phi_8_vec[p,t])
matrix_ON[x,y] <- fMRI_ON_Series[[p]][t]
matrix_OFF[x,y] <- fMRI_OFF_Series[[p]][t]
}
}
# smooth/blur the matrices
# matrix_ON_smooth <- (1/10000)*as.matrix(blur(as.im(matrix_ON), sigma=0.5))
# matrix_OFF_smooth <- (1/10000)*as.matrix(blur(as.im(matrix_OFF), sigma=0.5))
matrix_ON_smooth <- (1/10000)*as.matrix(blur(as.im(matrix_ON), sigma=0.5))
matrix_OFF_smooth <- (1/10000)*as.matrix(blur(as.im(matrix_OFF), sigma=0.5))
# Display the Re() and Im() parts of the complex k-space kimesurfaces
# Load necessary libraries
# Create sequences for x and y axes
x <- seq(1, 2*period+1, length.out = 2*period+1)
y <- seq(1, 2*period+1, length.out = 2*period+1)
# Create the first surface plot
pON_Re <- plot_ly(x=x, y=y, z=Re(matrix_ON), type="surface",
scene='scene1', name="Re(ON)") %>%
layout(title = "Re(fMRI-ON Basic Stiching)",
scene = list(domain=list(x=c(-period, period), y=c(-period, period)),
aspectmode = "cube")) %>% hide_colorbar()
pOFF_Re <- plot_ly(x=x, y=y, z=Re(matrix_OFF), type="surface",
scene='scene2', name="Re(OFF)") %>%
layout(title = "Re(fMRI-OFF Basic Stiching)",
scene = list(domain=list(x=c(-period, period), y=c(-period, period)),
aspectmode = "cube")) %>% hide_colorbar()
pDIFF_Re <- plot_ly(x=x, y=y, z=Re(matrix_ON-matrix_OFF), type="surface",
scene='scene3', name="Re(Diff)") %>%
layout(title = "Re(fMRI-ON - fMRI-OFF Basic Stiching)",
scene = list(domain=list(x=c(-period, period), y=c(-period, period)),
aspectmode = "cube")) %>% hide_colorbar()
# Scene titles
annotations=list(list(showarrow=FALSE, text='(ON)',
xref='scene',yref='scene',zref='scene', x=0.0,y=0.9,z=0.1,
xanchor='left',yanchor='bottom',font=list(size=16)),
list(showarrow=FALSE, text='(OFF)',
xref='scene2',yref='scene',zref='scene', x=0.35,y=0.9,z=0.1,
xanchor='left',yanchor='bottom',font=list(size=16)),
list(showarrow=FALSE, text='(DIFF)',
xref='scene3',yref='scene',zref='scene', x=0.7,y=0.9,z=0.1,
xanchor='left',yanchor='bottom',font=list(size=16)))
# Combine the plots
combined_plot <- subplot(pON_Re, pOFF_Re, pDIFF_Re) %>% #, nrows=2,margin=0.05) %>%
layout(title="Native-space Reconstruction of fMRI ON and OFF Kimesurfaces using Basic Stitching", annotations=annotations)
# Display the synchronized plot
combined_plot %>% htmlwidgets::onRender(
"function(x, el) {
x.on('plotly_relayout', function(d) {
const camera = Object.keys(d).filter((key) => /\\.camera$/.test(key));
if (camera.length) {
const scenes = Object.keys(x.layout).filter((key) => /^scene\\d*/.test(key));
const new_layout = {};
scenes.forEach(key => {
new_layout[key] = {...x.layout[key], camera: {...d[camera]}};
});
Plotly.relayout(x, new_layout);
}
});
}")
Next, we will examine the kimesurface reconstructions via k-space stitching together. This process uses random kime-phase draws, \(\varphi\sim\Phi_{[-\pi,\pi)}\), e.g., Laplace phase distribution) coupled with the complex-valued FT series of the original repeated measurement fMRI time-series into a kimesurface in the Fourier domain. We will contrast the IFT of the k-space constructed kimesurfaces back into the native space and characterize it’s local smoothness, relative to the smoothness of the kimesurface reconstructions obtained via basic stitching in the native space.
# 2. Apply the FT to each instance of the repeated measurement time-series.
library(spatstat)
FT_fMRI_ON_Series <- list()
FT_fMRI_OFF_Series <- list()
for (i in c(1:numSeries)) {
FT_fMRI_ON_Series[[i]] = fft(fMRI_ON_Series[[i]])
FT_fMRI_OFF_Series[[i]] = fft(fMRI_OFF_Series[[i]])
}
# 3. Stitch together (using random kime-phase draws, $\varphi\sim\Phi_{[-\pi,\pi)}$) all these complex-valued FT series into a kimesurface in k-space.
phi_8_vec <- matrix(NA, ncol=period, nrow = numSeries)
for (t in 1:period) {
# for a given t, generate 8 new phases
set.seed(t);
phi_8_vec[ ,t] <- extraDistr::rlaplace(numSeries, mu=0, sigma=0.5)
# rank-order the phases for consistency
# within the same foliation leaf
phi_8_vec[ ,t] <- sort(phi_8_vec[ ,t])
# force phases in [-pi: pi)
for (i in 1:numSeries) {
if (phi_8_vec[i,t] < -pi)
phi_8_vec[i,t] <- -pi
if (phi_8_vec[i,t] >= pi)
phi_8_vec[i,t] <- pi
}
}
matrix_ON <- matrix(0, nrow = 2*period+1, ncol = 2*period+1)
matrix_OFF <- matrix(0, nrow = 2*period+1, ncol = 2*period+1)
for (t in 1:period) { # POLAR to CARTESIAN coordinates
for (p in 1:numSeries) {
x = (period+1) + t*cos(phi_8_vec[p,t])
y = (period+1) + t*sin(phi_8_vec[p,t])
matrix_ON[x,y] <- FT_fMRI_ON_Series[[p]][t]
matrix_OFF[x,y] <- FT_fMRI_OFF_Series[[p]][t]
}
}
# smooth/blur the matrices
# matrix_ON_smooth <- (1/10000)*as.matrix(blur(as.im(matrix_ON), sigma=0.5))
# matrix_OFF_smooth <- (1/10000)*as.matrix(blur(as.im(matrix_OFF), sigma=0.5))
matrix_ON_smooth <- (1/10000)*as.matrix(blur(as.im(matrix_ON), sigma=0.5))
matrix_OFF_smooth <- (1/10000)*as.matrix(blur(as.im(matrix_OFF), sigma=0.5))
# Display the Re() and Im() parts of the complex k-space kimesurfaces
# Load necessary libraries
# library(plotly)
# library(htmlwidgets)
# Create sequences for x and y axes
x <- seq(1, 2*period+1, length.out = 2*period+1)
y <- seq(1, 2*period+1, length.out = 2*period+1)
# Create the first surface plot
pON_Re <- plot_ly(x=x, y=y, z=Re(matrix_ON), type="surface",
scene='scene1', name="Re(ON)") %>%
layout(title = "Re(fMRI-ON Kime Stiching)",
scene = list(domain=list(x=c(-period, period), y=c(-period, period)),
aspectmode = "cube")) %>% hide_colorbar()
pOFF_Re <- plot_ly(x=x, y=y, z=Re(matrix_OFF), type="surface",
scene='scene2', name="Re(OFF)") %>%
layout(title = "Re(fMRI-OFF Kime Stiching)",
scene = list(domain=list(x=c(-period, period), y=c(-period, period)),
aspectmode = "cube")) %>% hide_colorbar()
pDIFF_Re <- plot_ly(x=x, y=y, z=Re(matrix_ON-matrix_OFF), type="surface",
scene='scene3', name="Re(Diff)") %>%
layout(title = "Re(fMRI-ON - fMRI-OFF Kime Stiching)",
scene = list(domain=list(x=c(-period, period), y=c(-period, period)),
aspectmode = "cube")) %>% hide_colorbar()
pON_Im <- plot_ly(x=x, y=y, z=Im(matrix_ON), type = "surface",
scene='scene4', name="Im(On)") %>%
layout(title = "Im(fMRI-ON Kime Stiching)",
scene = list(domain=list(x=c(-period, period), y=c(-period, period)),
aspectmode = "cube")) %>% hide_colorbar()
pOFF_Im <- plot_ly(x=x, y=y, z=Im(matrix_OFF), type = "surface",
scene='scene5', name="Im(Off)") %>%
layout(title = "Im(fMRI-OFF Kime Stiching)") %>% hide_colorbar()
pDIFF_Im <- plot_ly(x=x, y=y, z=Im(matrix_ON-matrix_OFF), type = "surface",
scene='scene6', name="Im(Diff)") %>%
layout(title = "Im(fMRI-ON - fMRI-OFF Kime Stiching)",
scene = list(domain=list(x=c(-period, period), y=c(-period, period)),
aspectmode = "cube")) %>% hide_colorbar()
# Scene titles
annotations=list(list(showarrow=FALSE, text='Re(ON)',
xref='scene',yref='scene',zref='scene', x=0.0,y=0.9,z=0.1,
xanchor='left',yanchor='bottom',font=list(size=16)),
list(showarrow=FALSE, text='Re(OFF)',
xref='scene2',yref='scene',zref='scene', x=0.35,y=0.9,z=0.1,
xanchor='left',yanchor='bottom',font=list(size=16)),
list(showarrow=FALSE, text='Re(DIFF)',
xref='scene3',yref='scene',zref='scene', x=0.7,y=0.9,z=0.1,
xanchor='left',yanchor='bottom',font=list(size=16)),
list(showarrow=FALSE, text='Im(ON)',
xref='scene4',yref='scene',zref='scene', x=0.0,y=0,z=0.1,
xanchor='left',yanchor='bottom',font=list(size=16)),
list(showarrow=FALSE, text='Im(OFF)',
xref='scene5',yref='scene',zref='scene', x=0.35,y=0,z=0.1,
xanchor='left',yanchor='bottom',font=list(size=16)),
list(showarrow=FALSE, text='Im(DIFF)',
xref='scene6',yref='scene',zref='scene', x=0.7,y=0,z=0.1,
xanchor='left',yanchor='bottom',font=list(size=16)))
# Combine the plots
combined_plot <- subplot(pON_Re, pOFF_Re, pDIFF_Re,
pON_Im, pOFF_Im, pDIFF_Im) %>% #, nrows=2,margin=0.05) %>%
layout(title="Real (Re) and Imaginary (Im) fMRI-ON vs. fMRI-OFF Kime Stitching Surfaces",
scene=list(domain=list(x=c(0, 0.33), y=c(0.5,1)), aspectmode="cube"),
scene2=list(domain=list(x=c(0.33, 0.66), y=c(0.5,1)), aspectmode="cube"),
scene3=list(domain=list(x=c(0.7, 1), y = c(0.5,1)), aspectmode="cube"),
scene4=list(domain=list(x=c(0, 0.33), y=c(0.0,0.5)), aspectmode="cube"),
scene5=list(domain=list(x=c(0.33,0.66), y=c(0.0,0.5)), aspectmode="cube"),
scene6=list(domain=list(x=c(0.7, 1), y = c(0.0,0.5)), aspectmode="cube"),
annotations = annotations)
# Display the synchronized plot
combined_plot %>% htmlwidgets::onRender(
"function(x, el) {
x.on('plotly_relayout', function(d) {
const camera = Object.keys(d).filter((key) => /\\.camera$/.test(key));
if (camera.length) {
const scenes = Object.keys(x.layout).filter((key) => /^scene\\d*/.test(key));
const new_layout = {};
scenes.forEach(key => {
new_layout[key] = {...x.layout[key], camera: {...d[camera]}};
});
Plotly.relayout(x, new_layout);
}
});
}")
# 4. IFT the k-space constructed kimesurface into spacetime and
# characterize it's local smoothness, relative to the smoothness
# of the original k-space kimesurface.
# matrix_ON, matrix_OFF, and (matrix_ON - matrix_OFF)
IFT_matrix_ON <- fft(matrix_ON, inverse = T)
IFT_matrix_OFF <- fft(matrix_OFF, inverse = T)
# smooth/blur the matrices
# matrix_ON_smooth <- (1/10000)*as.matrix(blur(as.im(matrix_ON), sigma=0.5))
# matrix_OFF_smooth <- (1/10000)*as.matrix(blur(as.im(matrix_OFF), sigma=0.5))
Re_IFT_matrix_ON_smooth <-
(1/100)*as.matrix(blur(as.im(Re(IFT_matrix_ON)), sigma=2.0))
Re_IFT_matrix_OFF_smooth <-
(1/100)*as.matrix(blur(as.im(Re(IFT_matrix_OFF)), sigma=2.0))
Im_IFT_matrix_ON_smooth <-
(1/100)*as.matrix(blur(as.im(Im(IFT_matrix_ON)), sigma=2.0))
Im_IFT_matrix_OFF_smooth <-
(1/100)*as.matrix(blur(as.im(Im(IFT_matrix_OFF)), sigma=2.0))
Mag_IFT_matrix_ON_smooth <-
(1/100)*as.matrix(blur(as.im(Mod(IFT_matrix_ON)), sigma=2.0))
Mag_IFT_matrix_OFF_smooth <-
(1/100)*as.matrix(blur(as.im(Mod(IFT_matrix_OFF)), sigma=2.0))
# # Create sequences for x and y axes
# x <- seq(1, 2*period+1, length.out = 2*period+1)
# y <- seq(1, 2*period+1, length.out = 2*period+1)
# Create the first surface plot
pON_Re <- plot_ly(x=x, y=y, z=Re_IFT_matrix_ON_smooth, type="surface",
scene='scene1', name="Re(ON)") %>%
layout(title = "Re(fMRI-ON K-space Stiching)",
scene = list(domain=list(x=c(-period, period), y=c(-period, period)),
aspectmode = "cube")) %>% hide_colorbar()
pOFF_Re <- plot_ly(x=x, y=y, z=Re_IFT_matrix_OFF_smooth, type="surface",
scene='scene2', name="Re(OFF)") %>%
layout(title = "Re(fMRI-OFF K-space Stiching)",
scene = list(domain=list(x=c(-period, period), y=c(-period, period)),
aspectmode = "cube")) %>% hide_colorbar()
pDIFF_Re <- plot_ly(x=x, y=y,
z=(Re_IFT_matrix_ON_smooth - Re_IFT_matrix_OFF_smooth),
type="surface", scene='scene3', name="Re(Diff)") %>%
layout(title = "Re(fMRI-ON - fMRI-OFF K-space Stiching)",
scene = list(domain=list(x=c(-period, period), y=c(-period, period)),
aspectmode = "cube")) %>% hide_colorbar()
pON_Im <- plot_ly(x=x, y=y, z=Im_IFT_matrix_ON_smooth, type = "surface",
scene='scene4', name="Im(On)") %>%
layout(title = "Im(fMRI-ON K-space Stiching)",
scene = list(domain=list(x=c(-period, period), y=c(-period, period)),
aspectmode = "cube")) %>% hide_colorbar()
pOFF_Im <- plot_ly(x=x, y=y, z=Im_IFT_matrix_OFF_smooth, type = "surface",
scene='scene5', name="Im(Off)") %>%
layout(title = "Im(fMRI-OFF K-space Stiching)") %>% hide_colorbar()
pDIFF_Im <- plot_ly(x=x,y=y,z=(Im_IFT_matrix_ON_smooth - Im_IFT_matrix_OFF_smooth),
type="surface", scene='scene6', name="Im(Diff)") %>%
layout(title = "Im(fMRI-ON - fMRI-OFF K-space Stiching)",
scene = list(domain=list(x=c(-period, period), y=c(-period, period)),
aspectmode = "cube")) %>% hide_colorbar()
pON_Mag <- plot_ly(x=x, y=y, z=Mag_IFT_matrix_ON_smooth, type = "surface",
scene='scene7', name="Mag(On)") %>%
layout(title = "Mag(fMRI-ON K-space Stiching)",
scene = list(domain=list(x=c(-period, period), y=c(-period, period)),
aspectmode = "cube")) %>% hide_colorbar()
pOFF_Mag <- plot_ly(x=x, y=y, z=Mag_IFT_matrix_OFF_smooth, type = "surface",
scene='scene8', name="Mag(Off)") %>%
layout(title = "Mag(fMRI-OFF K-space Stiching)") %>% hide_colorbar()
pDIFF_Mag <- plot_ly(x=x,y=y,
z=(Mag_IFT_matrix_ON_smooth - Mag_IFT_matrix_OFF_smooth),
type="surface", scene='scene9', name="Im(Diff)") %>%
layout(title = "Mag(fMRI-ON - fMRI-OFF K-space Stiching)",
scene = list(domain=list(x=c(-period, period), y=c(-period, period)),
aspectmode = "cube")) %>% hide_colorbar()
# Scene titles
annotations=list(list(showarrow=FALSE, text='Re(ON)',
xref='scene',yref='scene',zref='scene', x=0.0,y=0.9,z=0.1,
xanchor='left',yanchor='bottom',font=list(size=16)),
list(showarrow=FALSE, text='Re(OFF)',
xref='scene2',yref='scene',zref='scene', x=0.35,y=0.9,z=0.1,
xanchor='left',yanchor='bottom',font=list(size=16)),
list(showarrow=FALSE, text='Re(DIFF)',
xref='scene3',yref='scene',zref='scene', x=0.7,y=0.9,z=0.1,
xanchor='left',yanchor='bottom',font=list(size=16)),
list(showarrow=FALSE, text='Im(ON)',
xref='scene4',yref='scene',zref='scene', x=0.0,y=0.5,z=0.1,
xanchor='left',yanchor='bottom',font=list(size=16)),
list(showarrow=FALSE, text='Im(OFF)',
xref='scene5',yref='scene',zref='scene', x=0.3,y=0.5,z=0.1,
xanchor='left',yanchor='bottom',font=list(size=16)),
list(showarrow=FALSE, text='Im(DIFF)',
xref='scene6',yref='scene',zref='scene', x=0.6,y=0.5,z=0.1,
xanchor='left',yanchor='bottom',font=list(size=16)),
list(showarrow=FALSE, text='Mag(ON)',
xref='scene7',yref='scene',zref='scene', x=0.0,y=0,z=0.1,
xanchor='left',yanchor='bottom',font=list(size=16)),
list(showarrow=FALSE, text='Mag(OFF)',
xref='scene8',yref='scene',zref='scene', x=0.35,y=0,z=0.1,
xanchor='left',yanchor='bottom',font=list(size=16)),
list(showarrow=FALSE, text='Mag(DIFF)',
xref='scene9',yref='scene',zref='scene', x=0.7,y=0,z=0.1,
xanchor='left',yanchor='bottom',font=list(size=16)))
# Combine the plots
combined_plot <- subplot(pON_Re, pOFF_Re, pDIFF_Re,
pON_Im, pOFF_Im, pDIFF_Im,
pON_Mag, pOFF_Mag, pDIFF_Mag) %>%
layout(title="Real (Re), Imaginary (Im) and Magnitude of\n the fMRI-ON vs. fMRI-OFF Fourier-space Stitching Surfaces",
scene=list(domain=list(x=c(0, 0.33), y=c(0.67,1)), aspectmode="cube"),
scene2=list(domain=list(x=c(0.33,0.66),y=c(0.67,1)), aspectmode="cube"),
scene3=list(domain=list(x=c(0.7, 1), y=c(0.67,1)), aspectmode="cube"),
scene4=list(domain=list(x=c(0, 0.33), y=c(0.33,0.66)), aspectmode="cube"),
scene5=list(domain=list(x=c(0.33,0.66),y=c(0.33,0.66)), aspectmode="cube"),
scene6=list(domain=list(x=c(0.7, 1), y = c(0.33,0.66)), aspectmode="cube"),
scene7=list(domain=list(x=c(0, 0.33), y=c(0.0,0.33)), aspectmode="cube"),
scene8=list(domain=list(x=c(0.33,0.66), y=c(0.0,0.33)), aspectmode="cube"),
scene9=list(domain=list(x=c(0.7, 1), y = c(0.0,0.33)), aspectmode="cube"),
annotations = annotations)
# Display the synchronized plot
combined_plot %>% htmlwidgets::onRender(
"function(x, el) {
x.on('plotly_relayout', function(d) {
const camera = Object.keys(d).filter((key) => /\\.camera$/.test(key));
if (camera.length) {
const scenes = Object.keys(x.layout).filter((key) => /^scene\\d*/.test(key));
const new_layout = {};
scenes.forEach(key => {
new_layout[key] = {...x.layout[key], camera: {...d[camera]}};
});
Plotly.relayout(x, new_layout);
}
});
}")
We can also consider using other integral transforms to obtain alternative kimesurface reconstructions in the native space.
For most of these examples, users may need to review/explore/extend the core functionality in the TCIU-package source-code (CRAN) to customize the TCIU functionality (GitHub) for the application specific needs!
The function fmri_image()
is used to create images for
front view, side view, and top view of the fMRI image.
The function fmri_ts_forecast()
fits the ARIMA
models for each voxel (spatial volume element) location.
This function is based on the TSplot_gen()
function from
package TSplotly
.
If there are concrete spatial locations (regions, voxels, coordinates) that are of specific interest, then one needs to focus on these locations. We’ll demonstrate this using fMRI brain activation (simulated) in the somatosensory (motor) cortex.
The function fmri_simulate_func()
is used to
simulate real-valued fMRI data with specified dimensions
(locations and extents).
The integrated function fmri_stimulus_detect()
is
designed to apply multiple analytical methods for activation detection,
in this case in the (human brain) motor area.
Examples of parametric and non-parametric statistical tests already built in include:
fmri_post_hoc()
.# statistical method HRF needs parameter ons and dur
pval1 = fmri_stimulus_detect(fmridata= bigim1_mod, mask = mask,
stimulus_idx = c(1:160)[rep(c(TRUE,FALSE), c(10,10))],
method = "HRF" ,
ons = c(1, 21, 41, 61, 81, 101, 121, 141),
dur = c(10, 10, 10, 10, 10, 10, 10, 10) )
# statistical method t-test for real-valued fMRI data
pval2 = fmri_stimulus_detect(fmridata= bigim1_mod, mask = mask,
stimulus_idx = c(1:160)[rep(c(TRUE,FALSE), c(10,10))],
method = "t-test")
# statistical method Wilk's Lambda for complex-valued data
pval3 = fmri_stimulus_detect(fmridata = bigim1, mask = mask,
stimulus_idx = c(1:160)[rep(c(TRUE,FALSE), c(10,10)) ],
method = "Wilks-Lambda" )
# do the fdr correction and the spatial clustering
# pval4 is the pval1 after the post-hoc processing
pval4 = fmri_post_hoc(pval1, fdr_corr = "fdr",
spatial_cluster.thr = 0.05,
spatial_cluster.size = 5,
show_comparison = FALSE)
Summarize the corresponding p-values.
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 0.0000 0.8987 1.0000 0.8463 1.0000 1.0000
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 0.0000096 0.8598920 1.0000000 0.8579245 1.0000000 1.0000000
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 0.0000025 0.1877401 1.0000000 0.7476717 1.0000000 1.0000000
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 0.0000 1.0000 1.0000 0.9971 1.0000 1.0000
Generate 2D plots of the 2D p-value (projection images) in sagittal, coronal and axial views.
Plot without isocontours.
for(axis in c("x", "y", "z")){
axis_i = switch(axis,
"x" = {35},
"y" = {30},
"z" = {22})
print(fmri_2dvisual(pval1, list(axis, axis_i),
hemody_data=NULL, mask=mask,
p_threshold = 0.05, legend_show = TRUE,
method = "scale_p",
color_pal = "YlOrRd", multi_pranges=TRUE))
}
Plot with isocontours.
The function fmri_pval_comparison_3d()
is used to
visualize two p-value maps showing their differences in detecting
stimulated brain areas in 3D scenes. Since our original fMRI is too big
to use here for different statistical tests, in this example, we compare
the differences of stimulated parts of two different fMRI data using the
same mask.
The function fmri_pval_comparison_2d()
displays the
p-values (generated by different statistical tests on the same fMRI
data) exposing their difference via 2D plots. For simplicity here we
only compare two different 3D arrays of p-values.
Below we demonstrate a three-tier statistical analysis (tri-phase) of regional activation.
Notes:
First, identify large local (regional areas) associated with activations/task stimuli.