Skip to content

Commit b6ce98b

Browse files
committed
Merge pull request #201 from matthew-brett/multi-run-eg-refactor
RF: Multi run example refactor Large expansion of comments and some refactoring to multi-run example, with JB and I reviewing.
2 parents dc1fb39 + ab51407 commit b6ce98b

File tree

1 file changed

+78
-55
lines changed

1 file changed

+78
-55
lines changed
Lines changed: 78 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,85 +1,108 @@
11
# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*-
22
# vi: set ft=python sts=4 ts=4 sw=4 et:
3-
""" This example is now out of date - please remind us to fix it
3+
""" Example of more than one run in the same model
44
"""
55
import numpy as np
6-
import sympy
76

87
from nipy.algorithms.statistics.api import Term, Formula, Factor
98
from nipy.modalities.fmri import utils, hrf
109

11-
# hrf
10+
# hrf models we will use for each run. Just to show it can be done, use a
11+
# different hrf model for each run
12+
h1 = hrf.glover
13+
h2 = hrf.afni
1214

13-
h1 = sympy.Function('hrf1')
14-
h2 = sympy.Function('hrf2')
15+
# Symbol for time in general. The 'events' function below will return models in
16+
# terms of 't', but we'll want models in terms of 't1' and 't2'. We need 't'
17+
# here so we can substitute.
1518
t = Term('t')
1619

17-
# Session 1
18-
19-
t1 = Term('t1')
20-
c11 = utils.events([3,7,10], f=h1); c11 = c11.subs(t, t1)
21-
c21 = utils.events([1,3,9], f=h1); c21 = c21.subs(t, t1)
22-
c31 = utils.events([2,4,8], f=h1); c31 = c31.subs(t, t1)
23-
d1 = utils.fourier_basis([0.3,0.5,0.7]); d1 = d1.subs(t, t1)
24-
tval1 = np.linspace(0,20,101)
25-
20+
# run 1
21+
t1 = Term('t1') # Time within run 1
22+
c11 = utils.events([3, 7, 10], f=h1) # Condition 1, run 1
23+
# The events utility returns a formula in terms of 't' - general time
24+
c11 = c11.subs(t, t1) # Now make it in terms of time in run 1
25+
# Same for conditions 2 and 3
26+
c21 = utils.events([1, 3, 9], f=h1); c21 = c21.subs(t, t1)
27+
c31 = utils.events([2, 4, 8], f=h1); c31 = c31.subs(t, t1)
28+
# Add also a fourier basis set for drift with frequencies 0.3, 0.5, 0.7
29+
d1 = utils.fourier_basis([0.3, 0.5, 0.7]); d1 = d1.subs(t, t1)
30+
31+
# Here's our formula for run 1 signal terms of time in run 1 (t1)
2632
f1 = Formula([c11,c21,c31]) + d1
2733

28-
# Session 2
29-
30-
t2 = Term('t2')
31-
c12 = utils.events([3.3,7,10], f=h2); c12 = c12.subs(t, t2)
32-
c22 = utils.events([1,3.2,9], f=h2); c22 = c22.subs(t, t2)
33-
c32 = utils.events([2,4.2,8], f=h2); c32 = c32.subs(t, t2)
34-
d2 = utils.fourier_basis([0.3,0.5,0.7]); d2 = d2.subs(t, t2)
35-
tval2 = np.linspace(0,10,51)
36-
37-
f2 = Formula([c12,c22,c32]) + d2
38-
39-
sess_factor = Factor('sess', [1,2])
40-
41-
# The multi session formula
42-
43-
f = Formula([sess_factor.terms[0]]) * f1 + Formula([sess_factor.terms[1]]) * f2
44-
45-
# Now, we evaluate the formula
46-
# the arrays now have 152=101+51 rows...
47-
34+
# run 2
35+
t2 = Term('t2') # Time within run 2
36+
# Conditions 1 through 3 in run 2
37+
c12 = utils.events([3.3, 7, 10], f=h2); c12 = c12.subs(t, t2)
38+
c22 = utils.events([1, 3.2, 9], f=h2); c22 = c22.subs(t, t2)
39+
c32 = utils.events([2, 4.2, 8], f=h2); c32 = c32.subs(t, t2)
40+
d2 = utils.fourier_basis([0.3, 0.5, 0.7]); d2 = d2.subs(t, t2)
41+
42+
# Formula for run 2 signal in terms of time in run 2 (t2)
43+
f2 = Formula([c12, c22, c32]) + d2
44+
45+
# Factor giving constant for run. The [1, 2] means that there are two levels to
46+
# this factor, and that when we get to pass in values for this factor,
47+
# instantiating an actual design matrix (see below), a value of 1 means level
48+
# 1 and a value of 2 means level 2.
49+
run_factor = Factor('run', [1, 2])
50+
run_1_coder = run_factor.get_term(1) # Term coding for level 1
51+
run_2_coder = run_factor.get_term(2) # Term coding for level 2
52+
53+
# The multi run formula will combine the indicator (dummy value) terms from the
54+
# run factor with the formulae for the runs (which are functions of (run1, run2)
55+
# time. The run_factor terms are step functions that are zero when not in the
56+
# run, 1 when in the run.
57+
f = Formula([run_1_coder]) * f1 + Formula([run_2_coder]) * f2 + run_factor
58+
59+
# Now, we evaluate the formula. So far we've been entirely symbolic. Now we
60+
# start to think about the values at which we want to evaluate our symbolic
61+
# formula.
62+
63+
# We'll use these values for time within run 1. The times are in seconds from
64+
# the beginning of run 1. In our case run 1 was 20 seconds long. 101 below
65+
# gives 101 values from 0 to 20 including the endpoints, giving a dt of 0.2.
66+
tval1 = np.linspace(0, 20, 101)
67+
# run 2 lasts 10 seconds. These are the times in terms of the start of run 2.
68+
tval2 = np.linspace(0, 10, 51)
69+
70+
# We pad out the tval1 / tval2 time vectors with zeros corresponding to the
71+
# TRs in run 2 / run 1.
4872
ttval1 = np.hstack([tval1, np.zeros(tval2.shape)])
4973
ttval2 = np.hstack([np.zeros(tval1.shape), tval2])
50-
session = np.array([1]*tval1.shape[0] + [2]*tval2.shape[0])
51-
52-
f = f.subs(h1, hrf.glover)
53-
f = f.subs(h2, hrf.glover)
74+
# The arrays above now have 152=101+51 rows...
5475

55-
# Create the recarray that will be used to create the design matrix
76+
# Vector of run numbers for each time point (with values 1 or 2)
77+
run_no = np.array([1]*tval1.shape[0] + [2]*tval2.shape[0])
5678

57-
rec = np.array([(t1,t2, s) for t1, t2, s in zip(ttval1, ttval2, session)],
79+
# Create the recarray that will be used to create the design matrix. The
80+
# recarray gives the actual values for the symbolic terms in the formulae. In
81+
# our case the terms are t1, t2, and the (indicator coding) terms from the run
82+
# factor.
83+
rec = np.array([(tv1, tv2, s) for tv1, tv2, s in zip(ttval1, ttval2, run_no)],
5884
np.dtype([('t1', np.float),
5985
('t2', np.float),
60-
('sess', np.int)]))
86+
('run', np.int)]))
6187

6288
# The contrast we care about
63-
64-
# It would be a good idea to be able to create
65-
# the contrast from the Formula, "f" above,
66-
# applying all of f's aliases to it....
67-
68-
sess_1 = sess_factor.get_term(1)
69-
sess_2 = sess_factor.get_term(2)
70-
contrast = Formula([sess_1*c11-sess_2*c12])
71-
contrast = contrast.subs(h1, hrf.glover)
72-
contrast = contrast.subs(h2, hrf.glover)
89+
contrast = Formula([run_1_coder * c11 - run_2_coder * c12])
7390

7491
# # Create the design matrix
75-
7692
X = f.design(rec, return_float=True)
7793

94+
# Show ourselves the design space covered by the contrast, and the corresponding
95+
# contrast matrix
7896
preC = contrast.design(rec, return_float=True)
79-
97+
# C is the matrix such that preC = X.dot(C.T)
8098
C = np.dot(np.linalg.pinv(X), preC)
8199
print C
82100

83-
nonzero = np.nonzero(np.fabs(C) >= 0.1)[0]
101+
# We can also get this by passing the contrast into the design creation.
102+
X, c = f.design(rec, return_float=True, contrasts=dict(C=contrast))
103+
assert np.allclose(C, c['C'])
104+
105+
# Show the names of the non-trivial elements of the contrast
106+
nonzero = np.nonzero(np.fabs(C) >= 1e-5)[0]
84107
print (f.dtype.names[nonzero[0]], f.dtype.names[nonzero[1]])
85-
print ((sess_2*c12).subs(h2, hrf.glover), (sess_1*c11).subs(h1, hrf.glover))
108+
print ((run_1_coder * c11), (run_2_coder * c12))

0 commit comments

Comments
 (0)