vneumodpy 0.1.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. vneumodpy/__init__.py +38 -0
  2. vneumodpy/glm/__init__.py +0 -0
  3. vneumodpy/glm/adjust_volume_dir.py +24 -0
  4. vneumodpy/glm/canonical_hrf.py +23 -0
  5. vneumodpy/glm/contrast_image.py +40 -0
  6. vneumodpy/glm/hrf_design_matrix.py +42 -0
  7. vneumodpy/glm/resampling_nifti_volume.py +73 -0
  8. vneumodpy/glm/roi_ts_from4dimage.py +39 -0
  9. vneumodpy/glm/roi_ts_to4dimage.py +31 -0
  10. vneumodpy/glm/tukey.py +97 -0
  11. vneumodpy/glm/tukey_mp.py +112 -0
  12. vneumodpy/glm/tukey_mth.py +112 -0
  13. vneumodpy/measures/__init__.py +0 -0
  14. vneumodpy/measures/ac.py +32 -0
  15. vneumodpy/measures/ccm.py +63 -0
  16. vneumodpy/measures/cm.py +31 -0
  17. vneumodpy/measures/cos_sim.py +22 -0
  18. vneumodpy/measures/dft.py +43 -0
  19. vneumodpy/measures/mskewkurt.py +30 -0
  20. vneumodpy/measures/mtess.py +309 -0
  21. vneumodpy/measures/pac.py +32 -0
  22. vneumodpy/measures/pccm.py +99 -0
  23. vneumodpy/measures/pccm_.py +81 -0
  24. vneumodpy/measures/pcm.py +56 -0
  25. vneumodpy/models/__init__.py +0 -0
  26. vneumodpy/models/group_range.py +30 -0
  27. vneumodpy/models/multivaliate_var_network.py +256 -0
  28. vneumodpy/models/mvar_init_with_cell_mp.py +69 -0
  29. vneumodpy/models/mvar_init_with_cell_mth.py +22 -0
  30. vneumodpy/models/regress.py +78 -0
  31. vneumodpy/nuisance/__init__.py +0 -0
  32. vneumodpy/nuisance/acompcor.py +61 -0
  33. vneumodpy/nuisance/mean_time_series.py +42 -0
  34. vneumodpy/nuisance/regression_out.py +21 -0
  35. vneumodpy/surrogate/__init__.py +0 -0
  36. vneumodpy/surrogate/dbs_multivariate_var.py +122 -0
  37. vneumodpy/surrogate/multivariate_var.py +91 -0
  38. vneumodpy/surrogate/vnm_addmul_signals.py +66 -0
  39. vneumodpy/surrogate/vnm_subject_perm.py +26 -0
  40. vneumodpy/surrogate/vnm_var_surrogate.py +53 -0
  41. vneumodpy-0.1.6.dist-info/METADATA +30 -0
  42. vneumodpy-0.1.6.dist-info/RECORD +45 -0
  43. vneumodpy-0.1.6.dist-info/WHEEL +5 -0
  44. vneumodpy-0.1.6.dist-info/licenses/LICENSE.md +23 -0
  45. vneumodpy-0.1.6.dist-info/top_level.txt +1 -0
vneumodpy/__init__.py ADDED
@@ -0,0 +1,38 @@
1
+
2
+ from .models.multivaliate_var_network import MultivariateVARNetwork
3
+ from .models.mvar_init_with_cell_mth import call_executor as mvar_init_with_cell_mth
4
+ from .models.regress import linear
5
+ from .models.regress import prepare
6
+ from .models.regress import inv_qr
7
+ from .models import group_range
8
+
9
+ from .glm.canonical_hrf import get as canonical_hrf
10
+ from .glm.hrf_design_matrix import get as hrf_design_matrix
11
+ from .glm.tukey import calc as tukey
12
+ from .glm.tukey_mp import calc as tukey_mp # multi processing version
13
+ from .glm.contrast_image import calc as contrast_image
14
+ from .glm.roi_ts_to4dimage import get as roi_ts_to4dimage
15
+ from .glm.roi_ts_from4dimage import get as roi_ts_from4dimage
16
+ from .glm.adjust_volume_dir import adjust_volume_dir
17
+ from .glm.resampling_nifti_volume import resampling_nifti_volume
18
+
19
+ from .surrogate.multivariate_var import calc as multivariate_var
20
+ from .surrogate.dbs_multivariate_var import calc as dbs_multivariate_var
21
+ from .surrogate.vnm_addmul_signals import get as vnm_addmul_signals
22
+ from .surrogate.vnm_var_surrogate import calc as vnm_var_surrogate
23
+ from .surrogate.vnm_subject_perm import get as vnm_subject_perm
24
+
25
+ from .measures import ac
26
+ from .measures import pac
27
+ from .measures import cm
28
+ from .measures import ccm
29
+ from .measures import pcm
30
+ from .measures import pccm
31
+ from .measures import pccm_
32
+ from .measures import mtess
33
+ from .measures import mskewkurt
34
+ from .measures.cos_sim import calc as cos_sim
35
+
36
+ from .nuisance.mean_time_series import get as nuisance_mean_time_series
37
+ from .nuisance.acompcor import get as nuisance_acompcor
38
+ from .nuisance.regression_out import get as nuisance_regression_out
File without changes
@@ -0,0 +1,24 @@
1
+ # -*- coding: utf-8 -*-
2
+ ##
3
+ # Adjust NIfTI volume direction based on NIfTI info Transpose matrix
4
+ # returns adjusted NIfTI volume
5
+ # input:
6
+ # V nifti 4D volume (X x Y x Z x frames)
7
+
8
+ from __future__ import print_function, division # for Python 2 compatible
9
+
10
+ import numpy as np
11
+
12
+
13
+ def adjust_volume_dir(V, info):
14
+ A = np.array([[0, 1, 1], [1, 0, 1], [1, 1, 0]])
15
+ if np.sum(np.abs(A * info.affine[:3, :3])) > 0:
16
+ print('nifti volume transformation is not supported.')
17
+ return V
18
+ if info.affine[0, 0] < 0: # check flip X axis
19
+ V = np.flipud(V).copy()
20
+ if info.affine[1, 1] < 0: # check flip Y axis
21
+ V = np.fliplr(V).copy()
22
+ if info.affine[2, 2] < 0: # check flip Z axis
23
+ V = np.flip(V, axis=2).copy()
24
+ return V
@@ -0,0 +1,23 @@
1
+ # -*- coding: utf-8 -*-
2
+ ##
3
+ # get GLM Canonical hemodynamic response function
4
+ # returns time range (t), HRF time-series (hrf)
5
+ # input:
6
+ # dt time resolution (sec) (default:0.045)
7
+ # responseDelay delay of response (gamma a)(sec) (default:6)
8
+ # underShootDelay delay of undershoot (gamma a)(sec) (default:16)
9
+ # kernelSec kernel time length (sec) (default: 32)
10
+ # underShootRatio ratio of underShoot (default: 0.167)
11
+ # hrfScale HRF scale (gamma b) (default: 0.9)
12
+
13
+ from __future__ import print_function, division # for Python 2 compatible
14
+
15
+ import numpy as np
16
+ from scipy.stats import gamma
17
+
18
+ def get(dt=0.045, response_delay=6, under_shoot_delay=16, kernel_sec=32, under_shoot_ratio=0.167, hrf_scale=0.9):
19
+
20
+ t = np.arange(0, kernel_sec+dt, dt)
21
+ hrf = gamma.pdf(t,response_delay,scale=hrf_scale) - gamma.pdf(t,under_shoot_delay,scale=hrf_scale) * under_shoot_ratio
22
+ hrf = hrf.T / np.sum(hrf)
23
+ return t, hrf
@@ -0,0 +1,40 @@
1
+ # -*- coding: utf-8 -*-
2
+ ##
3
+ # Calculate GLM Contrast Image with prewhitening.
4
+ # based on K.J.Friston et al. (2000), M.W.Woolrich et al. (2001), K.J.Worsley (2001)
5
+ # returns cells of T-value matrix (Ts)
6
+ # input:
7
+ # Cs cells of contrast vectors (contrasts (predictor size) x 1)
8
+ # B predictor variables (node x predictor variables)
9
+ # RSS Residual Sum of Squares (node x 1)
10
+ # X2is Vector or single value of inv(X' * X) for contrast
11
+ # tRs Vector or single value of trace(R) for contrast
12
+
13
+ from __future__ import print_function, division # for Python 2 compatible
14
+
15
+ import numpy as np
16
+ from math import sqrt
17
+
18
+ def calc(Cs, B, RSS, X2is, tRs):
19
+ # GLM contrast image
20
+ # T = (c' * B) / sqrt(c' * X' * inv(V) * X * c * (RSS / trace(R)))
21
+ Ts = []
22
+ roiNum = RSS.shape[0]
23
+ T2 = np.zeros((roiNum,1), dtype=np.float32)
24
+
25
+ for c in Cs:
26
+ for i in range(roiNum):
27
+ if X2is.shape[0] == roiNum:
28
+ X2i = X2is[i, :, :]
29
+ d = sqrt(c.T @ X2i @ c)
30
+ else:
31
+ d = sqrt(c.T @ X2is @ c)
32
+
33
+ if tRs.shape[0] == roiNum:
34
+ se2 = sqrt(RSS[i].item() / tRs[i].item()) # to scalar
35
+ else:
36
+ se2 = sqrt(RSS[i].item() / tRs) # to scalar
37
+
38
+ T2[i] = (c.T @ B[i, :].T) / (d * se2)
39
+ Ts.append(T2.copy())
40
+ return Ts
@@ -0,0 +1,42 @@
1
+ # -*- coding: utf-8 -*-
2
+ # get GLM HRF (hemodynamic response function) design matrix
3
+ # input:
4
+ # onsets cells of task set start time
5
+ # durations cells of task set duration
6
+ # frames fMRI time frames
7
+ # TR fMRI TR
8
+ # res sampling resolution of HRF
9
+ # sp sampling starting point (in resolution)
10
+ # hrf Canonical hemodynamic response function (optional)
11
+
12
+ from __future__ import print_function, division # for Python 2 compatible
13
+
14
+ import numpy as np
15
+
16
+ from ..glm.canonical_hrf import get as canonical_hrf # for command mode
17
+
18
+ def get(onsets, durations, frames, TR, res, sp, hrf=[]):
19
+
20
+ # get Canonical hemodynamic response function
21
+ if len(hrf) == 0:
22
+ dt = TR / res
23
+ t, hrf = canonical_hrf(dt)
24
+
25
+ tasknum = len(onsets)
26
+ X = np.zeros((frames * res, tasknum), float)
27
+ U = np.zeros((frames * res, tasknum), float)
28
+ for k in range(tasknum):
29
+ onset = onsets[k]
30
+ duration = durations[k]
31
+ for i in range(len(onset)):
32
+ t1 = int(np.ceil(onset[i] / TR * res))
33
+ t2 = int(np.ceil(t1 + duration[i] / TR * res))
34
+ U[t1-1:t2,k] = 1
35
+ # get design matrix
36
+ C = np.convolve(U[:,k], hrf)
37
+ X[:,k] = C[0:frames * res]
38
+
39
+ # final sampling
40
+ X = X[np.arange((sp-1),X.shape[0],res),:]
41
+ U = U[np.arange((sp-1),U.shape[0],res),:]
42
+ return X, U
@@ -0,0 +1,73 @@
1
+ # -*- coding: utf-8 -*-
2
+ # resampling NIfTI volume.
3
+ # returns re-sampled volume (outV)
4
+ # input:
5
+ # V nifti 3D volume (X x Y x Z)
6
+ # stepXY XY axes resampling rate
7
+ # stepZ Z axes resampling rate
8
+ # operation operation for each plane ('mode'(default),'max','min','mean','median')
9
+
10
+ from __future__ import print_function, division # for Python 2 compatible
11
+
12
+ import numpy as np
13
+ from scipy.stats import mode
14
+
15
+ def resampling_nifti_volume(V, stepXY, stepZ, operation='mode'):
16
+ xs = int(np.floor(V.shape[0] / stepXY))
17
+ ys = int(np.floor(V.shape[1] / stepXY))
18
+ zs = int(np.floor(V.shape[2] / stepZ))
19
+ outV = np.zeros((xs, ys, zs), dtype=np.float32)
20
+
21
+ if stepXY >= 1 and stepZ >= 1:
22
+ # scale down
23
+ for z in range(1,zs+1):
24
+ for y in range(1,ys+1):
25
+ for x in range(1,xs+1):
26
+ x_start = int(round(x * stepXY - (stepXY - 1))) - 1
27
+ x_end = int(round(x * stepXY))
28
+ y_start = int(round(y * stepXY - (stepXY - 1))) - 1
29
+ y_end = int(round(y * stepXY))
30
+ z_start = int(round(z * stepZ - (stepZ - 1))) - 1
31
+ z_end = int(round(z * stepZ))
32
+ A = V[x_start:x_end, y_start:y_end, z_start:z_end]
33
+
34
+ if operation == 'mode':
35
+ A_flat = A[~np.isnan(A)]
36
+ if A_flat.size == 0:
37
+ m = np.nan
38
+ else:
39
+ m = mode(A_flat, axis=None).mode[0]
40
+ elif operation == 'min':
41
+ m = np.nanmin(A)
42
+ elif operation == 'max':
43
+ m = np.nanmax(A)
44
+ elif operation == 'mean':
45
+ m = np.nanmean(A)
46
+ elif operation == 'median':
47
+ m = np.nanmedian(A)
48
+ else:
49
+ raise ValueError(f"Unsupported operation: {operation}")
50
+
51
+ outV[x-1, y-1, z-1] = m
52
+ else:
53
+ # scale up
54
+ out_shape = (
55
+ int(np.ceil(V.shape[0] / stepXY)),
56
+ int(np.ceil(V.shape[1] / stepXY)),
57
+ int(np.ceil(V.shape[2] / stepZ))
58
+ )
59
+ outV = np.zeros(out_shape, dtype=np.float32)
60
+
61
+ for z in range(1,V.shape[2]+1):
62
+ for y in range(1,V.shape[1]+1):
63
+ for x in range(1,V.shape[0]+1):
64
+ m = V[x-1, y-1, z-1]
65
+ xx_start = int(round((x-1) / stepXY))
66
+ xx_end = int(round(x / stepXY))
67
+ yy_start = int(round((y-1) / stepXY))
68
+ yy_end = int(round(y / stepXY))
69
+ zz_start = int(round((z-1) / stepZ))
70
+ zz_end = int(round(z / stepZ))
71
+ outV[xx_start:xx_end, yy_start:yy_end, zz_start:zz_end] = m
72
+
73
+ return outV
@@ -0,0 +1,39 @@
1
+ # -*- coding: utf-8 -*-
2
+ ##
3
+ # get ROI time-series (matrix) from NIfTI 4D volume.
4
+ # returns ROI time-series (X)
5
+ # input:
6
+ # V nifti 4D volume (X x Y x Z x frames)
7
+ # atlasV nifti 3D atlas (X x Y x Z)
8
+ # operation calc operation for each plane ('mode'(default),'max','min','mean','median','sum')
9
+
10
+ from __future__ import print_function, division # for Python 2 compatible
11
+
12
+ import numpy as np
13
+ from scipy.stats import mode
14
+
15
+ def get(V, atlasV, operation='mode'):
16
+ roiIdx = np.unique(atlasV)
17
+ roiIdx = roiIdx[roiIdx != 0] # remove 0
18
+
19
+ X = np.zeros((len(roiIdx), V.shape[3]), dtype=np.float32)
20
+ A = V.reshape(-1, V.shape[3])
21
+ for i in range(len(roiIdx)):
22
+ j = roiIdx[i]
23
+ B = A[atlasV.flatten() == j, :]
24
+ if operation == 'mode':
25
+ m = mode(B, axis=0, nan_policy='omit').mode[0]
26
+ elif operation == 'min':
27
+ m = np.nanmin(B, axis=0)
28
+ elif operation == 'max':
29
+ m = np.nanmax(B, axis=0)
30
+ elif operation == 'mean':
31
+ m = np.nanmean(B, axis=0)
32
+ elif operation == 'median':
33
+ m = np.nanmedian(B, axis=0)
34
+ elif operation == 'sum':
35
+ m = np.nansum(B, axis=0)
36
+ else:
37
+ raise ValueError(f"Unsupported operation: {operation}")
38
+ X[i, :] = m
39
+ return X
@@ -0,0 +1,31 @@
1
+ # -*- coding: utf-8 -*-
2
+ ##
3
+ # get NIfTI 4D volume from ROI time-series (matrix).
4
+ # returns nifti 4D volume (V)(X x Y x Z x frames)
5
+ # input:
6
+ # X ROI time-series (ROIs x frames)
7
+ # atlasV nifti 3D atlas (X x Y x Z)
8
+
9
+ from __future__ import print_function, division # for Python 2 compatible
10
+
11
+ import numpy as np
12
+
13
+ def get(X, atlasV):
14
+ roinum = X.shape[0]
15
+
16
+ xs, ys, zs = atlasV.shape
17
+ V = np.full((xs, ys, zs, X.shape[1]), np.nan, dtype=np.float32)
18
+
19
+ idxs = [None] * roinum
20
+ aVf = atlasV.flatten()
21
+ aVfidx = np.array(range(aVf.shape[0]))
22
+ for i in range(roinum):
23
+ idxs[i] = aVfidx[aVf == (i+1)]
24
+
25
+ for t in range(X.shape[1]):
26
+ A = np.full((xs, ys, zs), np.nan, dtype=np.float32)
27
+ for i in range(roinum):
28
+ A.flat[idxs[i]] = X[i, t]
29
+ V[:, :, :, t] = A
30
+
31
+ return V
vneumodpy/glm/tukey.py ADDED
@@ -0,0 +1,97 @@
1
+ # -*- coding: utf-8 -*-
2
+ ##
3
+ # Calculate General Linear Model with prewhitening (Tukey-Taper, Auto-correlation estimation Version)
4
+ # based on M.W.Woolrich et al. (2001)
5
+ # Y = X * B + e
6
+ # e ~ N(0, s^2 * V)
7
+ # S * Y = S * X * B + r
8
+ # S = inv(K)
9
+ # V = K * K'
10
+ # V is auto-corerlation matrix estimated by Tukey-Taper
11
+ # r ~ N(0, s^2 * SVS') therefore N(0, s^2 * I)
12
+ # This function is used for single session.
13
+ # returns predictor variables (B), Residual Sum of Squares (RSS), degree of freedom (df),
14
+ # inv(X' * X) for contrast (X2is), trace(R) for contrast (tRs), full of Residuals (R)
15
+ # input:
16
+ # Y ROI or voxel time series (time series x node)
17
+ # X design matrix (time series x predictor variables)
18
+ # tuM Tukey-Taper window size (default: sqrt(time series length))
19
+
20
+ from __future__ import print_function, division # for Python 2 compatible
21
+
22
+ import numpy as np
23
+ import time
24
+ from scipy.linalg import toeplitz, cholesky, inv
25
+ from scipy.stats import linregress
26
+
27
+ def calc(Y, X, tuM=None, isOutX2is=False):
28
+ if tuM is None:
29
+ tuM = int(np.floor(np.sqrt(X.shape[0])))
30
+
31
+ print('process GLM with Tukey-Taper(' +str(tuM)+ ') estimation ...')
32
+ roiNum = Y.shape[1]
33
+ xsz = X.shape[1]
34
+ X2is = np.full((roiNum, xsz, xsz), np.nan, dtype=np.float32)
35
+ tRs = np.full((roiNum,1), np.nan, dtype=np.float32)
36
+ B = np.full((roiNum, xsz), np.nan, dtype=np.float32)
37
+ RSS = np.full((roiNum,1), np.nan, dtype=np.float32)
38
+ df = X.shape[0] - X.shape[1]
39
+
40
+ # make Tukey window
41
+ tuWin = np.zeros(tuM, dtype=np.float32)
42
+ for k in range(tuM):
43
+ tuWin[k] = 0.5 * (1 + np.cos(np.pi * (k+1) / tuM))
44
+
45
+ def regress(y, X):
46
+ # Equivalent to MATLAB regress: returns b, residuals
47
+ b, _, _, _ = np.linalg.lstsq(X, y, rcond=None)
48
+ r = y - X @ b
49
+ return b, r
50
+
51
+ start = time.time()
52
+ for i in range(roiNum):
53
+ Yi = Y[:, i]
54
+ # 1st step OLS regression
55
+ _, r = regress(Yi, X)
56
+
57
+ # if residuals are all zero, probably original signal is just zero. ignore this voxel
58
+ if np.sum(r == 0) == r.shape[0]:
59
+ B[i, :] = 0
60
+ RSS[i] = 0
61
+ continue
62
+
63
+ # calc AR coefficients by Tukey-Taper of frequency domain
64
+ C = np.correlate(r, r, mode='full') / np.dot(r, r)
65
+ mid = len(C) // 2
66
+ Rxx = C[mid:mid + tuM]
67
+ Pxx = np.zeros(r.shape[0], dtype=np.float32)
68
+ Pxx[:tuM] = Rxx[:tuM] * tuWin[:tuM]
69
+ V1 = toeplitz(Pxx)
70
+ K1 = np.linalg.cholesky(V1) # upper=False is not necessary (numpy v1)
71
+
72
+ # second time regression
73
+ # Ki1 = np.linalg.inv(K1)
74
+ # Ya = Ki1 @ Yi
75
+ # Xt = Ki1 @ X
76
+ Ya = np.linalg.solve(K1, Yi)
77
+ Xt = np.linalg.solve(K1, X)
78
+ b, r = regress(Ya, Xt)
79
+
80
+ B[i, :] = b
81
+ RSS[i] = r.T @ r
82
+ if not isOutX2is:
83
+ continue
84
+
85
+ # used for contrast
86
+ C = np.correlate(r, r, mode='full') / np.dot(r, r)
87
+ Rxx = C[mid:mid + tuM]
88
+ Pxx = np.zeros(r.shape[0], dtype=np.float32)
89
+ Pxx[:tuM] = Rxx[:tuM] * tuWin[:tuM]
90
+
91
+ V2 = toeplitz(Pxx)
92
+ X2is[i, :, :] = inv(X.T @ (inv(V2) @ X))
93
+ IR = np.eye(Xt.shape[0]) - Xt @ inv(Xt.T @ Xt) @ Xt.T
94
+ tRs[i] = np.trace(IR)
95
+
96
+ print('done t=' + str(time.time() - start) + ' sec')
97
+ return B, RSS, df, X2is, tRs
@@ -0,0 +1,112 @@
1
+ # -*- coding: utf-8 -*-
2
+ ##
3
+ # Calculate General Linear Model with prewhitening (Tukey-Taper, Auto-correlation estimation Version)
4
+ # based on M.W.Woolrich et al. (2001)
5
+ # Y = X * B + e
6
+ # e ~ N(0, s^2 * V)
7
+ # S * Y = S * X * B + r
8
+ # S = inv(K)
9
+ # V = K * K'
10
+ # V is auto-corerlation matrix estimated by Tukey-Taper
11
+ # r ~ N(0, s^2 * SVS') therefore N(0, s^2 * I)
12
+ # This function is used for single session.
13
+ # returns predictor variables (B), Residual Sum of Squares (RSS), degree of freedom (df),
14
+ # inv(X' * X) for contrast (X2is), trace(R) for contrast (tRs), full of Residuals (R)
15
+ # input:
16
+ # Y ROI or voxel time series (time series x node)
17
+ # X design matrix (time series x predictor variables)
18
+ # tuM Tukey-Taper window size (default: sqrt(time series length))
19
+
20
+ from __future__ import print_function, division # for Python 2 compatible
21
+
22
+ import numpy as np
23
+ import time
24
+ import os
25
+ from scipy.linalg import toeplitz, cholesky, inv
26
+ from scipy.stats import linregress
27
+ from concurrent.futures import ProcessPoolExecutor
28
+
29
+
30
+ def regress(y, X):
31
+ # Equivalent to MATLAB regress: returns b, residuals
32
+ b, _, _, _ = np.linalg.lstsq(X, y, rcond=None)
33
+ r = y - X @ b
34
+ return b, r
35
+
36
+ def loop_fn(i, Yi, X, tuWin, tuM, isOutX2is):
37
+ # 1st step OLS regression
38
+ _, r = regress(Yi, X)
39
+
40
+ # if residuals are all zero, probably original signal is just zero. ignore this voxel
41
+ if np.sum(r == 0) == r.shape[0]:
42
+ return i, 0, 0
43
+
44
+ # calc AR coefficients by Tukey-Taper of frequency domain
45
+ C = np.correlate(r, r, mode='full') / np.dot(r, r)
46
+ mid = len(C) // 2
47
+ Rxx = C[mid:mid + tuM]
48
+ Pxx = np.zeros(r.shape[0], dtype=np.float32)
49
+ Pxx[:tuM] = Rxx[:tuM] * tuWin[:tuM]
50
+ V1 = toeplitz(Pxx)
51
+ K1 = np.linalg.cholesky(V1) # upper=False is not necessary (numpy v1)
52
+
53
+ # second time regression
54
+ # Ki1 = np.linalg.inv(K1)
55
+ # Ya = Ki1 @ Yi
56
+ # Xt = Ki1 @ X
57
+ Ya = np.linalg.solve(K1, Yi)
58
+ Xt = np.linalg.solve(K1, X)
59
+ b, r = regress(Ya, Xt)
60
+
61
+ rss = r.T @ r
62
+ if not isOutX2is:
63
+ return i, b, rss
64
+
65
+ # used for contrast
66
+ C = np.correlate(r, r, mode='full') / np.dot(r, r)
67
+ Rxx = C[mid:mid + tuM]
68
+ Pxx = np.zeros(r.shape[0], dtype=np.float32)
69
+ Pxx[:tuM] = Rxx[:tuM] * tuWin[:tuM]
70
+
71
+ V2 = toeplitz(Pxx)
72
+ x2is = inv(X.T @ (inv(V2) @ X))
73
+ IR = np.eye(Xt.shape[0]) - Xt @ inv(Xt.T @ Xt) @ Xt.T
74
+ trs = np.trace(IR)
75
+ return i, b, rss, x2is, trs
76
+
77
+ def calc(Y, X, tuM=None, isOutX2is=False, n_jobs=8):
78
+ if tuM is None:
79
+ tuM = int(np.floor(np.sqrt(X.shape[0])))
80
+
81
+ print('process GLM with Tukey-Taper(' +str(tuM)+ ') estimation ...')
82
+ roiNum = Y.shape[1]
83
+ xsz = X.shape[1]
84
+ X2is = np.full((roiNum, xsz, xsz), np.nan, dtype=np.float32)
85
+ tRs = np.full((roiNum,1), np.nan, dtype=np.float32)
86
+ B = np.full((roiNum, xsz), np.nan, dtype=np.float32)
87
+ RSS = np.full((roiNum,1), np.nan, dtype=np.float32)
88
+ df = X.shape[0] - X.shape[1]
89
+
90
+ # make Tukey window
91
+ tuWin = np.zeros(tuM, dtype=np.float32)
92
+ for k in range(tuM):
93
+ tuWin[k] = 0.5 * (1 + np.cos(np.pi * (k+1) / tuM))
94
+
95
+ start = time.time()
96
+ with ProcessPoolExecutor(max_workers=n_jobs) as executor: # -----(2)
97
+ futures = set()
98
+ for i in range(roiNum):
99
+ future = executor.submit(loop_fn, i, Y[:, i], X, tuWin, tuM, isOutX2is)
100
+ futures.add(future)
101
+
102
+ for f in futures:
103
+ r = f.result()
104
+ i = r[0]
105
+ B[i, :] = r[1]
106
+ RSS[i] = r[2]
107
+ if isOutX2is:
108
+ X2is[i, :, :] = r[3]
109
+ tRs[i] = r[4]
110
+
111
+ print('done t=' + str(time.time() - start) + ' sec')
112
+ return B, RSS, df, X2is, tRs
@@ -0,0 +1,112 @@
1
+ # -*- coding: utf-8 -*-
2
+ ##
3
+ # Calculate General Linear Model with prewhitening (Tukey-Taper, Auto-correlation estimation Version)
4
+ # based on M.W.Woolrich et al. (2001)
5
+ # Y = X * B + e
6
+ # e ~ N(0, s^2 * V)
7
+ # S * Y = S * X * B + r
8
+ # S = inv(K)
9
+ # V = K * K'
10
+ # V is auto-corerlation matrix estimated by Tukey-Taper
11
+ # r ~ N(0, s^2 * SVS') therefore N(0, s^2 * I)
12
+ # This function is used for single session.
13
+ # returns predictor variables (B), Residual Sum of Squares (RSS), degree of freedom (df),
14
+ # inv(X' * X) for contrast (X2is), trace(R) for contrast (tRs), full of Residuals (R)
15
+ # input:
16
+ # Y ROI or voxel time series (time series x node)
17
+ # X design matrix (time series x predictor variables)
18
+ # tuM Tukey-Taper window size (default: sqrt(time series length))
19
+
20
+ from __future__ import print_function, division # for Python 2 compatible
21
+
22
+ import numpy as np
23
+ import time
24
+ import os
25
+ from scipy.linalg import toeplitz, cholesky, inv
26
+ from scipy.stats import linregress
27
+ from concurrent.futures import ThreadPoolExecutor
28
+
29
+
30
+ def regress(y, X):
31
+ # Equivalent to MATLAB regress: returns b, residuals
32
+ b, _, _, _ = np.linalg.lstsq(X, y, rcond=None)
33
+ r = y - X @ b
34
+ return b, r
35
+
36
+ def loop_fn(i, Yi, X, tuWin, tuM, isOutX2is):
37
+ # 1st step OLS regression
38
+ _, r = regress(Yi, X)
39
+
40
+ # if residuals are all zero, probably original signal is just zero. ignore this voxel
41
+ if np.sum(r == 0) == r.shape[0]:
42
+ return i, 0, 0
43
+
44
+ # calc AR coefficients by Tukey-Taper of frequency domain
45
+ C = np.correlate(r, r, mode='full') / np.dot(r, r)
46
+ mid = len(C) // 2
47
+ Rxx = C[mid:mid + tuM]
48
+ Pxx = np.zeros(r.shape[0], dtype=np.float32)
49
+ Pxx[:tuM] = Rxx[:tuM] * tuWin[:tuM]
50
+ V1 = toeplitz(Pxx)
51
+ K1 = np.linalg.cholesky(V1) # upper=False is not necessary (numpy v1)
52
+
53
+ # second time regression
54
+ # Ki1 = np.linalg.inv(K1)
55
+ # Ya = Ki1 @ Yi
56
+ # Xt = Ki1 @ X
57
+ Ya = np.linalg.solve(K1, Yi)
58
+ Xt = np.linalg.solve(K1, X)
59
+ b, r = regress(Ya, Xt)
60
+
61
+ rss = r.T @ r
62
+ if not isOutX2is:
63
+ return i, b, rss
64
+
65
+ # used for contrast
66
+ C = np.correlate(r, r, mode='full') / np.dot(r, r)
67
+ Rxx = C[mid:mid + tuM]
68
+ Pxx = np.zeros(r.shape[0], dtype=np.float32)
69
+ Pxx[:tuM] = Rxx[:tuM] * tuWin[:tuM]
70
+
71
+ V2 = toeplitz(Pxx)
72
+ x2is = inv(X.T @ (inv(V2) @ X))
73
+ IR = np.eye(Xt.shape[0]) - Xt @ inv(Xt.T @ Xt) @ Xt.T
74
+ trs = np.trace(IR)
75
+ return i, b, rss, x2is, trs
76
+
77
+ def calc(Y, X, tuM=None, isOutX2is=False):
78
+ if tuM is None:
79
+ tuM = int(np.floor(np.sqrt(X.shape[0])))
80
+
81
+ print('process GLM with Tukey-Taper(' +str(tuM)+ ') estimation ...')
82
+ roiNum = Y.shape[1]
83
+ xsz = X.shape[1]
84
+ X2is = np.full((roiNum, xsz, xsz), np.nan, dtype=np.float32)
85
+ tRs = np.full((roiNum,1), np.nan, dtype=np.float32)
86
+ B = np.full((roiNum, xsz), np.nan, dtype=np.float32)
87
+ RSS = np.full((roiNum,1), np.nan, dtype=np.float32)
88
+ df = X.shape[0] - X.shape[1]
89
+
90
+ # make Tukey window
91
+ tuWin = np.zeros(tuM, dtype=np.float32)
92
+ for k in range(tuM):
93
+ tuWin[k] = 0.5 * (1 + np.cos(np.pi * (k+1) / tuM))
94
+
95
+ start = time.time()
96
+ with ThreadPoolExecutor(max_workers=os.cpu_count() // 2) as executor: # -----(2)
97
+ futures = set()
98
+ for i in range(roiNum):
99
+ future = executor.submit(loop_fn, i, Y[:, i], X, tuWin, tuM, isOutX2is)
100
+ futures.add(future)
101
+
102
+ for f in futures:
103
+ r = f.result()
104
+ i = r[0]
105
+ B[i, :] = r[1]
106
+ RSS[i] = r[2]
107
+ if isOutX2is:
108
+ X2is[i, :, :] = r[3]
109
+ tRs[i] = r[4]
110
+
111
+ print('done t=' + str(time.time() - start) + ' sec')
112
+ return B, RSS, df, X2is, tRs
File without changes
@@ -0,0 +1,32 @@
1
+ # -*- coding: utf-8 -*-
2
+ ##
3
+ # Calculate auto-correlation (simple version)
4
+ # returns matrix (node x lags)
5
+ # input:
6
+ # X multivariate time series matrix (node x time series)
7
+ # max_lag time lags for Auto-Correlation function (default: 15)
8
+
9
+ from __future__ import print_function, division
10
+
11
+ import numpy as np
12
+ import matplotlib.pyplot as plt
13
+ import statsmodels.api as sm
14
+
15
+
16
+ def calc(x, max_lag=15):
17
+ node_num = x.shape[0]
18
+ xr = np.zeros((node_num, max_lag+1), dtype=x.dtype)
19
+ for i in range(node_num):
20
+ xr[i, :] = sm.tsa.stattools.acf(x[i, :], nlags=max_lag)
21
+ return xr
22
+
23
+
24
+ def plot(x, max_lag=15):
25
+ xr = calc(x=x, max_lag=max_lag)
26
+ plt.matshow(xr, vmin=-1, vmax=1)
27
+ plt.colorbar()
28
+ plt.xlabel('Source Nodes')
29
+ plt.ylabel('Target Nodes')
30
+ plt.show(block=False)
31
+ plt.pause(1)
32
+ return xr