๐ŸฉปfMRI

3. Design of experiments

date
Aug 5, 2023
slug
fmri-glm-3
author
status
Public
tags
NI-edu
Dartbrain
summary
type
Post
thumbnail
category
๐ŸฉปfMRI
updatedAt
Aug 5, 2023 11:04 AM

Types of designs

Factorial designs

  • ์ง€๋‚œ ์‹œ๊ฐ„์— ๋ฐฐ์› ๋˜ t-test์—์„œ ํ™œ์šฉ๋˜๋Š” contrast vector๋ฅผ ์„ธ์›Œ๋ณด๋Š” ์—ฐ์Šต์„ ํ•ด๋ณด์ž
    • (1) red squares, (2) red circles, (3) green squares, (4) green circles๋กœ ์ž๊ทน์ด ์ฃผ์–ด์กŒ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด๋ณด์ž
exp_length = 160
TR = 1 # TR์ด 1์ธ ๊ฒฝ์šฐ์—๋Š” ๋‹ค์šด์ƒ˜ํ”Œ๋ง์„ ๋ฌด์‹œํ•  ์ˆ˜ ์žˆ์Œ
xmax = exp_length // TR

y, X = simulate_signal(
    onsets=np.array(
        [0, 40, 80, 120,  # red squares
         10, 50, 90, 130,  # red circles
         20, 60, 100, 140,  # green squares
         30, 70, 110, 150]  # green circles
    ),
    conditions=['rq'] * 4 + ['rc'] * 4 + ['gs'] * 4 + ['gc'] * 4,
    TR=TR,
    duration=exp_length,
    icept=0,
    params_canon=[1, 2, 3, 4],
    rnd_seed=42,
    plot=False
)

X = X[:, :5]

plt.figure(figsize=(15, 5))
plt.plot(X[:, 1], c='r', ls='--')
plt.plot(X[:, 2], c='r')
plt.plot(X[:, 3], c='g', ls='--')
plt.plot(X[:, 4], c='g')
plt.legend(['red squares', 'red circles', 'green squares', 'green circles'], frameon=False)
notion image
plt.plot(y)
plt.xlim(0, xmax)
notion image
simulate_signal code
def simulate_signal(onsets, conditions, TR=2, duration=None, icept=0, params_canon=None, params_tderiv=None,
                    params_deriv1=None, phi=None, std_noise=1, osf=100, rnd_seed=None, plot=True):
    """ Simulates a somewhat realistic voxel signal with an associated design matrix. """
    if rnd_seed is not None:
        np.random.seed(rnd_seed)
    
    conds = sorted(np.unique(conditions))
    P = len(conds)
    
    if duration is None:
        duration = np.max(onsets) + 30
    
    if params_canon is None:
        params_canon = np.zeros(P)

    # TMP FIX
    params_tderiv = params_deriv1
    
    if params_tderiv is None:
        params_tderiv = np.zeros(P)

    X = np.zeros((duration * osf, P))
    for ons, con in zip(onsets, conditions):
        X[int(ons * osf), conds.index(con)] = 1
        
    hrf = glover_hrf(tr=1, oversampling=osf)
    hrf_d = glover_time_derivative(tr=1, oversampling=osf)

    t_orig = np.arange(0, duration, 1 / osf)
    t_new = np.arange(0, duration, TR)
    
    Xconv = np.zeros((t_orig.size, P * 2))
    idx = 0
    for i, this_hrf in enumerate([hrf, hrf_d]):
        this_hrf /= this_hrf.max()
        for ii in range(P):
            Xconv[:, idx] = np.convolve(X[:, ii], this_hrf)[:t_orig.size]  
            idx += 1
    
    Xconv = np.c_[np.ones(Xconv.shape[0]), Xconv]
    params = np.r_[icept, params_canon, params_tderiv]
    y = Xconv @ params
    
    resampler = interp1d(t_orig, y)
    y = resampler(t_new)       
    
    if phi is None:
        noise_cov = std_noise ** 2 * np.eye(y.size)
    else:
        noise_cov = std_noise ** 2 * phi ** toeplitz(np.arange(y.size))
    
    y = y + np.random.multivariate_normal(np.zeros(y.size), noise_cov)

    Xconv_ds = np.zeros((t_new.size, Xconv.shape[1]))
    for i in range(Xconv.shape[1]):
        resampler = interp1d(t_orig, Xconv[:, i])
        Xconv_ds[:, i] = resampler(t_new)
    
    est_betas = np.linalg.lstsq(Xconv_ds, y, rcond=None)[0]
    if plot:
        plt.figure(figsize=(15, 5))
        plt.plot(y)
        plt.plot(Xconv_ds @ est_betas)
        plt.xlim(0, y.size)
        plt.legend(['y', 'y-hat'])
        plt.show()

    return y, Xconv_ds
Questions
Question 1.
GLM ๋ชจ๋ธ์„ ๋งŒ๋“ค์–ด๋ผ.
print(y.shape, X.shape) # X: intercept ํฌํ•จ๋˜์–ด ์žˆ์Œ

from numpy.linalg import inv

betas_todo = inv(X.T @ X) @ X.T @ y
ย 
Question 2.
๋นจ๊ฐ„์ƒ‰ ์ž๊ทน์ด ๋…น์ƒ‰ ์ž๊ทน๋ณด๋‹ค ๋” ๋งŽ์€ ํ™œ๋™์„ ์ผ์œผํ‚จ๋‹ค๋Š” ๊ฐ€์„ค์„ ์„ธ์šฐ๋ ค๊ณ  ํ•œ๋‹ค. ์ด์— ๋งž๋Š” contrast vector๋ฅผ ๋งŒ๋“ค์–ด๋ผ.
cvec_red_green = np.array([0, 1, 1, -1, -1])
ย 
Question 3.
์› ์ž๊ทน์ด ์‚ฌ๊ฐํ˜• ์ž๊ทน๋ณด๋‹ค ๋” ๋งŽ์€ ํ™œ๋™์„ ์ผ์œผํ‚จ๋‹ค๋Š” ๊ฐ€์„ค์„ ์„ธ์šฐ๋ ค๊ณ  ํ•œ๋‹ค. ์ด์— ๋งž๋Š” contrast vector๋ฅผ ๋งŒ๋“ค์–ด๋ผ.
cvec_circle_square = np.array([0, -1, 1, -1, 1])
ย 
Question 4.
๋นจ๊ฐ„์ƒ‰ ์ž๊ทน์ด ๋…น์ƒ‰ ์ž๊ทน๋ณด๋‹ค ๋” ๋งŽ์€ ํ™œ๋™์„ ์ผ์œผํ‚ค๋ฉด์„œ, ์› ์ž๊ทน์ด ์‚ฌ๊ฐํ˜• ์ž๊ทน๋ณด๋‹ค ๋” ๋งŽ์€ ํ™œ๋™์„ ์ผ์œผํ‚จ๋‹ค๋Š” ๊ฐ€์„ค์„ ์„ธ์šฐ๋ ค๊ณ  ํ•œ๋‹ค. ์ด์— ๋งž๋Š” contrast vector๋ฅผ ๋งŒ๋“ค์–ด๋ผ.
cvec_interaction = np.array([0, -1, 1, 1, -1])

Parametric designs

  • ๋ณ€์ˆ˜๋Š” categorical๋ฟ๋งŒ ์•„๋‹ˆ๋ผ continuous, ordinalํ•  ์ˆ˜ ์žˆ์Œ
  • ์ด๋ ‡๊ฒŒ ์ง€์†์ ์œผ๋กœ ์˜ํ–ฅ์„ ๋ฏธ์น˜๋Š” ๋ณ€์ˆ˜๋ฅผ ๊ฐ€์ง€๊ณ  ๊ฐ€์„ค๋กœ ์„ค์ •ํ•˜๋Š” ๊ฒƒ์„ parametric design์ด๋ผ๊ณ  ๋ถ€๋ฆ„
  • parametric design์€ ๋‘ ๊ฐ€์ง€ ๋ฐฉ์‹์œผ๋กœ ๋ณต์…€ ๋ฐ˜์‘์— ์˜ํ–ฅ์„ ๋ฏธ์นœ๋‹ค๊ณ  ๊ฐ€์ •ํ•จ
    • An โ€œunmodulatedโ€ response
    • A parametric modulation of the response
notion image
  • ์œ„์˜ ์˜ˆ์‹œ์—์„œ ๊ฐ€์„ค์€ ๋ณด์ƒ ๊ทœ๋ชจ์— ์˜ํ•œ ๋ฐ˜์‘์„ ๋ณด์ด๋Š” ๋ณต์…€ ์˜์—ญ์ด ์žˆ๋Š”์ง€ ์ฐพ๋Š” ๊ฒƒ์ž„
np.random.seed(42)
exp_time = 60 * 10  # i.e., 14 minutes in seconds
n_trials = 30
reward_onsets = np.arange(4, exp_time, exp_time / n_trials).astype(int) # size: 30

reward_magnitudes = np.random.randint(1, 11, size=reward_onsets.size) / 10

plt.figure(figsize=(15, 5))
plt.plot(reward_magnitudes, marker='o')
notion image
  • ์œ„์˜ ๊ฐ€์„ค์— ๋”ฐ๋ผ ์ž๊ทน์ด ์—†์„ ๋•Œ 0๊ณผ, ์ž๊ทน์ด ์žˆ์„ ๋•Œ 1์„ ๊ฐ€์ง„ ํšŒ๊ท€์‹์„ ๋งŒ๋“ค ๊ฒƒ์ž„
  • ์ด ๋•Œ, โ€œunmodulatedโ€ response, parametric modulation of the response์„ ๋งŒ์กฑํ•˜๋Š” ์กฐ๊ฑด์„ ๊ฐ€์ง€๋Š” ๋‘ ๊ฐœ์˜ ํšŒ๊ท€์‹์„ ๋งŒ๋“ฆ
    • regressor of โ€œunmodulatedโ€ response
    • hrf = glover_hrf(tr=1, oversampling=1)
      
      x_unmod = np.zeros(exp_time)
      x_unmod[reward_onsets] = 1
      x_unmod = np.convolve(x_unmod, hrf)[:exp_time] # โ€œunmodulatedโ€ response
      
      plt.figure(figsize=(15, 5))
      plt.plot(x_unmod)
      notion image
    • regressor of parametric modulation of the response
      • ๋ณด์ƒ์˜ ํฌ๊ธฐ - ๋ณด์ƒ์˜ ํ‰๊ท  ํฌ๊ธฐ๋ฅผ ํ™œ์šฉํ•ด์„œ ํšŒ๊ท€์‹์„ ๋งŒ๋“ฆ
      • unmodulated regressor์—์„œ modulated regressor๋งŒํผ decorrelate ํ•˜๊ธฐ ๋•Œ๋ฌธ์ž„
      • def parametric_modulation(reward_onsets, reward_magnitudes, exp_time, hrf, ans):
            reward_magnitudes_ms = reward_magnitudes - reward_magnitudes.mean() # parametric modulation of the response
            x_mod = np.zeros(exp_time)
            x_mod[reward_onsets] = reward_magnitudes_ms
            x_mod = np.convolve(x_mod, hrf)[:exp_time]
  • modulated regressor: ์ž๊ทน์— ๋Œ€ํ•œ ์ผ๋ฐ˜์ ์ธ ๋ฐ˜์‘์ด ์•„๋‹ˆ๋ผ modulation of response์— ๋Œ€ํ•œ ๋ถ„์‚ฐ๋งŒ ๊ณ ๋ คํ•จ

Design variance/efficiency

Optimizing effects

Psychological factors

  • โ€œAre subjects really doing/thinking the way I want them to?โ€
  • โ€œAm I really measuring what I think Iโ€™m measuring?โ€

Design factors

  • design variance

Optimizing design variance

  • design variance๊ฐ€ ๋†’์œผ๋ฉด ํšจ์œจ์„ฑ์ด ๋‚ฎ๊ณ , design variance๊ฐ€ ๋‚ฎ์œผ๋ฉด ํšจ์œจ์„ฑ์ด ๋†’์Œ (design variance๋Š” ๊ฐ ๋ณ€์ˆ˜์˜ variance์™€ covariance๋กœ ๊ตฌ์„ฑ๋จ)
  • ํšจ์œจ์ ์ธ ์„ค๊ณ„๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์š”์ธ์„ ๊ณ ๋ คํ•˜๋ฉด ๋จ
    • high varianceย (i.e. they should vary a lot relative to their mean)
    • low covarianceย (i.e. they should not correlate between each other a lot)
  • ์—ฌ๊ธฐ์„œ ๋Š” ๋ถ„์‚ฐ-๊ณต๋ถ„์‚ฐ ํ–‰๋ ฌ์„, ๋Š” contrast vector๋ฅผ ์˜๋ฏธํ•จ
  • ํฐ ํšจ๊ณผ(t)๋ฅผ ์–ป๊ธฐ ์œ„ํ•ด์„œ๋Š” ์„ธ๊ฐ€์ง€๊ฐ€ ํ•„์š”ํ•จ
    • A large response/effect (ฮฒ)
    • An efficient design (high variance, low covariance)
    • Low noise/unexplained variance (lowย SSEย /ย DF)
def conceptual_tvalue_calculator(signal, noise, design_variance):
    """ Calculate the effect (t-value) from the signal, noise, and design variance components.
    
    Parameters
    ----------
    signal : int/float
    noise : int/float
    design_variance : int/float
    
    Returns
    -------
    effect : float
    efficiency : float
    """
    efficiency = 1 / design_variance
    effect = signal / (noise * design_variance)
    return effect, efficiency

SIGNAL = 0.5
NOISE = 2.8
DESIGN_VARIANCE = 0.02
design_var = c @ inv(X.T @ X) @ c.T
efficiency = 1.0 / c @ inv(X.T @ X) @ c.T

The effect of predictor variance on design variance/efficiency

  • ๋ณ€์ˆ˜์˜ ๊ฐ’์ด ํ‰๊ท ์—์„œ ๋” ๋งŽ์ด ๋ฒ—์–ด๋‚ ์ˆ˜๋ก, ๋ถ„์‚ฐ์€ ์ปค์ง
notion image
  • ํฐ ๋ถ„์‚ฐ์„ ๊ฐ€์ง„ ๋ฐ์ดํ„ฐ๋Š” ํฐ MSE๋ฅผ ๊ฐ€์ง€๋ฉฐ, ๋†’์€ t-value๋ฅผ ๊ฐ€์ง
    • ํฐ ๋ถ„์‚ฐ์„ ๊ฐ€์ง„ ๋ฐ์ดํ„ฐ๋Š” ์ž‘์€ ๋ถ„์‚ฐ์„ ๊ฐ€์ง„ ๋ฐ์ดํ„ฐ๋ณด๋‹ค design variance๊ฐ€ ์ ๋‹ค๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•จ
ย 

The effect of predictor covariance on design variance/efficiency

Multicollinearity

  • ๋ณ€์ˆ˜๋“ค ๊ฐ„์˜ ๊ณต๋ถ„์‚ฐ์„ multicollinearity๋ผ๊ณ  ๋ถ€๋ฆ„
  • ๋ณ€์ˆ˜๋“ค ๊ฐ„์˜ ์—ฐ๊ด€์„ฑ์ด ๋†’์€ ๊ฒฝ์šฐ, ์–ด๋–ค ๋ณ€์ˆ˜์— ์˜ํ•œ ๋ณ€ํ™”์ธ์ง€ ์ •ํ™•ํ•˜๊ฒŒ ์ธก์ •ํ•  ์ˆ˜ ์—†์–ด์„œ efficiency๊ฐ€ ๋‚ฎ์Œ
  • (if correlation is high, then the โ€˜ambiguity, about the source of an effect is also high)
from scipy.stats import pearsonr

corr = pearsonr(X[:, 1], X[:, 2])[0]
eff = 1.0 / cvec.dot(np.linalg.inv(X.T.dot(X))).dot(cvec.T)

Evaluating multiple contrasts

  • ํ•˜๋‚˜์˜ ๊ธฐ์ค€์ด ์•„๋‹Œ ์—ฌ๋Ÿฌ ๊ธฐ์ค€(A, B, A-B)์— ๋Œ€ํ•œ ๋ณ€ํ™”๋ฅผ ์ธก์ •ํ•˜๊ณ  ์‹ถ์„ ๋•Œ ์‚ฌ์šฉํ•จ
  • ์•„๋ž˜์˜ ์˜ˆ์‹œ๋Š” contrast vector๋Š” A, B, A-B์— ๋Œ€ํ•œ ๋ณ€ํ™”๋ฅผ ์ธก์ •ํ•จ
def overall_eff(X):
    cvecs = [np.array([0, 1, 0]), np.array([0, 0, 1]), np.array([0, 1, -1])]
    overall_eff_ans = len(cvecs) / np.sum([cvec @ inv(X.T @ X) @ cvec.T for cvec in cvecs])

How should we design our experiment to maximize efficiency?

Estimation vs. detection

1) [Majority research] You want to know whether different conditions activate voxel activity differently.
โ†’ HRF-amplitude ์—๋งŒ ์ง‘์ค‘ํ•˜๋ฉด ๋จ (detection)
2) [Small portion] You want investigate how different conditions influence voxel activity not only by investigating the โ€œamplitudeโ€ parameter of the HRF, but also parameters relating to other properties of the shape of the HRF (like width, lag, strenght of undershoot, etc.).
โ†’ HRF ๋ชจ์–‘์ด ๋‚˜์ด ๋“ฑ์— ๋”ฐ๋ผ ๋‹ฌ๋ผ์งˆ ์ˆ˜ ์žˆ์Œ
โ†’ HRF ํ•จ์ˆ˜์—์„œ ๋‹ค์–‘ํ•œ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•จ

Event-related vs. blocked designs

  • Event-related designs: ๋‹ค๋ฅธ ์ž๊ทน์ด ๋ฌด์ž‘์œ„๋กœ ์‹œํ–‰
  • Blocked designs: ๊ฐ™์€ ์ž๊ทน์ด ๋ธ”๋ก์œผ๋กœ ๊ทธ๋ฃนํ™”๋˜์–ด ์‹œํ–‰
np.random.seed(2)

N = 180
dg_hrf = glover_hrf(tr=1, oversampling=1)

# Blocked designs
blocked_pred1_onsets = list(range(10, 30)) + list(range(90, 110))
blocked_pred2_onsets = list(range(50, 70)) + list(range(130, 150))
N_stim = len(blocked_pred1_onsets)
blocked_pred1, blocked_pred2 = np.zeros(N), np.zeros(N)
blocked_pred1[blocked_pred1_onsets] = 1
blocked_pred2[blocked_pred2_onsets] = 1

icept = np.ones((N, 1))
X_blocked = np.hstack((
    icept,
    np.convolve(blocked_pred1, dg_hrf)[:N, np.newaxis],
    np.convolve(blocked_pred2, dg_hrf)[:N, np.newaxis]
))

# Event-related designs
er_stims = np.arange(N)
er_pred1_onsets = np.random.choice(er_stims, N_stim, replace=False)
er_stims_new = np.array([o for o in er_stims if o not in er_pred1_onsets])
er_pred2_onsets = np.random.choice(er_stims_new, N_stim, replace=False)
er_pred1, er_pred2 = np.zeros(N), np.zeros(N)
er_pred1[er_pred1_onsets] = 1
er_pred2[er_pred2_onsets] = 1

X_er = np.hstack((
    icept,
    np.convolve(er_pred1, dg_hrf)[:N, np.newaxis],
    np.convolve(er_pred2, dg_hrf)[:N, np.newaxis]
))
notion image
  • ์šฐ๋ฆฌ๊ฐ€ detection์— ํฌ์ปค์‹ฑ์„ ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด, covariance๊ฐ€ ๋‚ฎ๊ณ (correlation), variance๊ฐ€ ๋†’์€(spread) blocked design์„ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.
corr_blocked = pearsonr(X_blocked[:, 1], X_blocked[:, 2])
corr_er = pearsonr(X_er[:, 1], X_er[:, 2])
print(corr_blocked, corr_er)
>>> -0.238, -0.268

The โ€œparadoxโ€ of efficiency

  • ์šฐ๋ฆฌ๋Š” ์‚ด์•„์žˆ๋Š” ์ธ๊ฐ„์˜ fMRI ๋ฐ์ดํ„ฐ๋ฅผ scanํ•˜๋ฏ€๋กœ, ํšจ์œจ์ ์ธ Blocked designs์—๋งŒ ์ง‘์ค‘ํ•˜๋Š” ๊ฒƒ์€ ์˜ค๋ฅ˜๋ฅผ ๋‚ณ์„ ์ˆ˜ ์žˆ์Œ
  • fMRI ์‹คํ—˜ ์ž์ฒด์˜ ํŠน์ง•(์ง€๊ฒจ์›€, ์˜ˆ์ธก๊ฐ€๋Šฅ์„ฑ, ์Šต๊ด€, ๋ถˆํ•„์š”ํ•œ ์›€์ง์ž„ ๋“ฑ)๋“ค์— ์˜ํ•ด์„œ ์˜ค๋ฅ˜์— ๋น ์งˆ ์ˆ˜ ์žˆ์Œ
  • ํŠนํžˆ ๋ถˆํ•„์š”ํ•œ ์›€์ง์ž„์œผ๋กœ ์ธํ•ด LTI(Linear time-invariant)๊ฐ€ ์ƒ๊ฒจ BOLD ์‹ ํ˜ธ๋ฅผ ๋ฐฉํ•ดํ•จ
  • ์‹ฌ๋ฆฌ์  ์š”์ธ ๋“ฑ์˜ ๊ฐ€๋Šฅ์„ฑ์„ ์ค„์ด๊ธฐ ์œ„ํ•ด, semi-random event-related designs์„ ํ™œ์šฉํ•  ํ•„์š”๊ฐ€ ์žˆ์Œ
  • ๊ทธ๋ ‡๋‹ค๋ฉด, event-related designs์„ ์–ด๋–ป๊ฒŒ ๋””์ž์ธํ•ด์•ผ ์ตœ์ ํ™”ํ•  ์ˆ˜ ์žˆ์„์ง€ ๊ณ ๋ฏผํ•ด๋ณผ ํ•„์š” ์žˆ์Œ

Improving design efficiency for event-related designs using jittering

  • ์ž๊ทน์ด ์—†๋Š” ๋™์•ˆ์˜ ์งง์€ ๊ธฐ๊ฐ„์„ ISI(inter-stimulus interval)์ด๋ผ๊ณ  ๋ถ€๋ฆ„
  • ์•„๋ž˜ ์˜ˆ์‹œ๋Š” 8์ดˆ์˜ ISI(์ž๊ทน์˜ ๊ฐ„๊ฒฉ)๋ฅผ ๊ฐ€์ง
def simulate_data_fixed_ISI(N=420):

    dg_hrf = glover_hrf(tr=1, oversampling=1)
    
    # Create indices in regularly spaced intervals (9 seconds, i.e. 1 sec stim + 8 ISI)
    stim_onsets = np.arange(10, N - 15, 9)
    stimcodes = np.repeat([1, 2], stim_onsets.size / 2)  # create codes for two conditions
    np.random.shuffle(stimcodes)  # random shuffle
    stim = np.zeros((N, 1))

    c = np.array([[0, 1, 0], [0, 0, 1]])

    # Fill stim array with codes at onsets
    for i, stim_onset in enumerate(stim_onsets):
        stim[stim_onset] = 1 if stimcodes[i] == 1 else 2
    
    stims_A = (stim == 1).astype(int)
    stims_B = (stim == 2).astype(int)

    reg_A = np.convolve(stims_A.squeeze(), dg_hrf)[:N]
    reg_B = np.convolve(stims_B.squeeze(), dg_hrf)[:N]
    X = np.hstack((np.ones((reg_B.size, 1)), reg_A[:, np.newaxis], reg_B[:, np.newaxis]))
    dvars = [(c[i, :].dot(np.linalg.inv(X.T.dot(X))).dot(c[i, :].T))
             for i in range(c.shape[0])]
    eff = c.shape[0] / np.sum(dvars)
    return X, eff

X, eff = simulate_data_fixed_ISI()
notion image
ย 
  • ์œ„์™€ ๋‹ฌ๋ฆฌ ISI๊ฐ€ ์‹œ๋„ ๋•Œ๋งˆ๋‹ค ๋‹ค์–‘ํ•ด์งˆ ์ˆ˜ ์žˆ์Œ โ†’ ์ด๋ฅผ โ€˜jitteringโ€™์ด๋ผ๊ณ  ๋ถ€๋ฆ„
  • jittering์€ covariance๋ฅผ ์ค„์ด๊ณ  variance๋ฅผ ๋†’์ผ ์ˆ˜ ์žˆ์Œ
  • ์•„๋ž˜ ์˜ˆ์‹œ๋Š” 2-6์ดˆ์˜ (๊ฐ€๋ณ€) ISI๋ฅผ ๊ฐ€์ง
def simulate_data_jittered_ISI(N=420):
    
    dg_hrf = glover_hrf(tr=1, oversampling=1)
    
    stim_onsets = np.arange(10, N - 15, 9)
    stimcodes = np.repeat([1, 2], stim_onsets.size / 2)
    np.random.shuffle(stimcodes)

    # Here, we pick some *deviations* from the standard ISI (i.e., 8),
    # so possible ISIs are (8 - 2, 8 - 1, 8 - 0, 8 + 1, 8 + 2)
    ISIs = np.repeat([-2, -1, 0, 1, 2], repeats=11)
    np.random.shuffle(ISIs)
    
    stim = np.zeros((N, 1))
    c = np.array([[0, 1, 0], [0, 0, 1]])

    for i, stim_onset in enumerate(stim_onsets):
        # We subtract the stim-onset with -2, -1, 0, 1, or 2 (from ISIs)
        # to simulate jittering
        stim[stim_onset - ISIs[i]] = 1 if stimcodes[i] == 1 else 2
        
    stims_A = (stim == 1).astype(int)
    stims_B = (stim == 2).astype(int)
    reg_A = np.convolve(stims_A.squeeze(), dg_hrf)[:N]
    reg_B = np.convolve(stims_B.squeeze(), dg_hrf)[:N]
    X = np.hstack((np.ones((reg_B.size, 1)), reg_A[:, np.newaxis], reg_B[:, np.newaxis]))

    # Loop over the two contrasts
    dvars = [(c[i, :].dot(np.linalg.inv(X.T.dot(X))).dot(c[i, :].T))
             for i in range(c.shape[0])]
    eff = c.shape[0] / np.sum(dvars)
    return X, eff
notion image