Skip to content

Commit c4dacdf

Browse files
some formatting updates, fixed the order of the source time series
1 parent 45cda1c commit c4dacdf

File tree

1 file changed

+55
-42
lines changed

1 file changed

+55
-42
lines changed

exercises_text/ni2_inverse5_scanning.md

+55-42
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ Up until now we have explored the covariance between two variables represented a
107107

108108
Let’s go back now to our basic observation model: `X = L*S+n`. If we now change this equation to include the individual contributions of the single sources, and forget about the noise we get:
109109

110-
X = l1*s1 + l2*s2 + … + ln*sn;
110+
X = l1*s1 + l2*s2 + … + ln*sn
111111

112112
When we compute `X*X'` and observe the individual terms in `X` we are essentially doing the following:
113113

@@ -118,14 +118,14 @@ There is a basic rule in matrix algebra that states that `(A*B)'` is the same as
118118
C = l1*s1*s1'*l1' + l2*s2*s1'*l1' + … + ln*sn*s1'*l1' + …
119119
l1*s1*s2'*l2' + l2*s2*s2'*l2' + … + ln*sn*s2'*l2' + …
120120
121-
l1*s1*sn'*ln' + l2*s2*sn'*ln' + … + ln*sn*sn'*ln';
121+
l1*s1*sn'*ln' + l2*s2*sn'*ln' + … + ln*sn*sn'*ln'
122122

123123
Now the above equation looks complicated, but if we realize that `si*sj'` represents the covariance (or auto-covariance) between the sources `i` and `j`, and that these are scalar values (which means that it does not matter in which order you multiply this in a matrix multiplication), we can rewrite the above equation as:
124124

125-
C = var(s1)*l1*l1' + cov(s2, s1)*l2*l1' + … + cov(sn, s1)*ln*l1' + …
126-
cov(s1, s2)*l1*l2' + var(s2)*l2*l2' + … + cov(sn, s2)*ln*l2' + …
127-
128-
cov(s1, sn)*l1*ln' + cov(s2, sn)*l2*ln' + … + var(sn)*ln*ln';
125+
C = var(s1) *l1*l1' + cov(s2, s1)*l2*l1' + … + cov(sn, s1)*ln*l1' + …
126+
cov(s1, s2)*l1*l2' + var(s2) *l2*l2' + … + cov(sn, s2)*ln*l2' + …
127+
128+
cov(s1, sn)*l1*ln' + cov(s2, sn)*l2*ln' + … + var(sn) *ln*ln'
129129

130130
The terms `li*lj'` are also called vector outer-products, so in words the above equation means that the covariance matrix can be represented as a _weighted sum_ of the leadfield outer products between all _pairs of sources_, where the weights are based on the covariance between the corresponding pairs of sources.
131131

@@ -176,18 +176,18 @@ The filter output is defined as: `w'*X`
176176

177177
Let us now create some simulated data to demonstrate the beamformer.
178178

179-
sens = ni2_sensors('type', 'meg');
179+
sens = ni2_sensors('type', 'meg');
180180
headmodel = ni2_headmodel('type', 'spherical', 'nshell', 1);
181181

182-
leadfield1 = ni2_leadfield(sens,headmodel,[-2 -7.8 3 -1 0 0]); % position 1110 in the grid
183-
leadfield2 = ni2_leadfield(sens,headmodel,[-5.3 0 5.9 1 0 0]); % position 2342 in the grid
184-
leadfield3 = ni2_leadfield(sens,headmodel,[4.9 0 6.2 0 1 0]); % position 2352 in the grid
185-
leadfield4 = ni2_leadfield(sens,headmodel,[4.2 -2 7 0 0.2 0.7]); % position 2674 in the grid
182+
leadfield1 = ni2_leadfield(sens, headmodel, [-2 -7.8 3 -1 0 0]); % position 1110 in the grid
183+
leadfield2 = ni2_leadfield(sens, headmodel, [-5.3 0 5.9 1 0 0]); % position 2342 in the grid
184+
leadfield3 = ni2_leadfield(sens, headmodel, [4.9 0 6.2 0 1 0]); % position 2352 in the grid
185+
leadfield4 = ni2_leadfield(sens, headmodel, [4.2 -2 7 0 0.2 0.7]); % position 2674 in the grid
186186

187-
[s1, t1] = ni2_activation('latency', .45, 'frequency', 3);
188-
[s2, t2] = ni2_activation('latency', .50, 'frequency', 10);
189-
[s3, t3] = ni2_activation('latency', .50, 'frequency', 15);
190-
[s4, t4] = ni2_activation('latency', .55, 'frequency', 30);
187+
[s1, t] = ni2_activation('latency', .45, 'frequency', 3);
188+
[s2, t] = ni2_activation('latency', .50, 'frequency', 10);
189+
[s3, t] = ni2_activation('latency', .50, 'frequency', 15);
190+
[s4, t] = ni2_activation('latency', .55, 'frequency', 30);
191191

192192
sensordata = leadfield1*s1 + leadfield2*s2 + leadfield3*s3 + leadfield4*s4 + randn(301, 1000)*0.04e-8;
193193

@@ -221,28 +221,35 @@ Now we also compute the inverse of the covariance matrix, because it will be use
221221

222222
> We can now compute the source time courses reconstructed with the beamformer. Do this and call the result `sbf`.
223223
224-
We can now inspect what the shape of the reconstructed source time course is at the locations at which activity was simulated. Note that if we don’t constrain the orientation of the sources (i.e., use a 3-column leadfield per location) we will get a 3-row spatial filter per location. In order to go from the original position-based indices of the grid points, we need to do the following:
224+
We can now inspect what the shape of the reconstructed source time course is at the locations at which activity was simulated. Note that if we don’t constrain the orientation of the sources (i.e., use a 3-column leadfield per location) we will get a 3-row spatial filter per location. In order to go from the original 3D grid indices that cover a regular gfrid inside _and_ outside the head, we need to do the following:
225225

226-
sel = find(ismember(find(sourcemodel.inside), [1110 2342 2352 2674]));
227-
sel = repmat((sel-1)*3, 1, 3)+repmat(1:3, numel(sel), 1);
226+
index = 1:numel(sourcemodel.inside); % these are all source positions, including the ones outside the brain
227+
index1110 = find(index(sourcemodel.inside)==1110) % find the index of source position 1110, only considering the ones inside the brain
228+
index2342 = find(index(sourcemodel.inside)==2342)
229+
index2352 = find(index(sourcemodel.inside)==2352)
230+
index2674 = find(index(sourcemodel.inside)==2674)
231+
sel1110 = (index1110-1)*3 + (1:3) % find the three columns corresponding to source position 1110
232+
sel2342 = (index2342-1)*3 + (1:3)
233+
sel2352 = (index2352-1)*3 + (1:3)
234+
sel2674 = (index2674-1)*3 + (1:3)
228235

229-
Each row in the matrix `sel` is a triplet of consecutive numbers that points to rows in the matrix of `wbf` (and `sbf`) that we are going to explore first.
236+
Each vector `selXXX` is a triplet of consecutive numbers that points to rows in the matrix of `wbf` (and `sbf`) that we are going to explore first.
230237

231-
figure;
232-
subplot(1, 2, 1); plot(t1, sbf(sel(1,:),:));
233-
subplot(1, 2, 2); plot(t1, s4);
238+
figure
239+
subplot(1, 2, 1); plot(t, sbf(sel1110,:));
240+
subplot(1, 2, 2); plot(t, s1);
234241

235-
figure;
236-
subplot(1, 2, 1); plot(t1, sbf(sel(2,:),:));
237-
subplot(1, 2, 2); plot(t1, s2);
242+
figure
243+
subplot(1, 2, 1); plot(t, sbf(sel2342,:));
244+
subplot(1, 2, 2); plot(t, s2);
238245

239-
figure;
240-
subplot(1, 2, 1); plot(t1, sbf(sel(3,:),:));
241-
subplot(1, 2, 2); plot(t1, s1);
246+
figure
247+
subplot(1, 2, 1); plot(t, sbf(sel2352,:));
248+
subplot(1, 2, 2); plot(t, s3);
242249

243-
figure;
244-
subplot(1, 2, 1); plot(t1, sbf(sel(4,:),:));
245-
subplot(1, 2, 2); plot(t1, s3);
250+
figure
251+
subplot(1, 2, 1); plot(t, sbf(sel2674,:));
252+
subplot(1, 2, 2); plot(t, s4);
246253

247254
As you may have noticed, the resulting time courses are a bit noisy. This is due to the noise in the data being projected onto the estimated source time courses. This can be accounted for by a mathematical trick that is called regularization. This boils down to adding a scaled identity matrix to the sensor covariance matrix prior to calculating the inverse. This makes the mathematical inversion of the covariance matrix less sensitive to. The regularized inverse of the covariance matrix can be computed as:
248255

@@ -269,20 +276,24 @@ To illustrate this depth bias, we first restructure the source-reconstructed sim
269276
In order to represent the reconstruction as an image, we need to express the variance of the sources at each of the locations in a single number. Recall that at each location of the 3-dimensional grid the source activity consists of three time courses, one for each 'cardinal’ x/y/z orientation. A common way to achieve this is to sum the variance across the three orientations. This is essentially the application of Pythagoras’ rule (without explicitly taking the square and the square root, since variance is already a 'squared’ value). With our sbf variable it is possible to do this in the following way. Note that this variable is a 'number of inside sources x 3’ times 'number of timepoints’ matrix. If we compute the variance across time (`var(sbf, [], 2)`) we end up with a vector, that in consecutive triplets contains the variance in the x/y/z orientation at the 'inside’ dipole locations of the sourcemodel. We can now efficiently create the variance for each location by using MATLAB’s reshape and sum functions:
270277

271278
pbf = var(sbf, [], 2);
272-
pbf = sum(reshape(pbf, 3, numel(pbf)/3));
279+
pbf = reshape(pbf, 3, numel(pbf)/3);
280+
pbf = sum(pbf, 1);
273281

274-
Take a moment to try and understand what is going on in the second line.
282+
Take a moment to try and understand what is going on in the second and third line.
275283

276284
Now, create a FieldTrip type 'source’-structure, and use ft_sourceplot to visualize this.
277285

286+
source = [];
278287
source.pos = sourcemodel.pos;
279288
source.dim = sourcemodel.dim;
280289
source.inside = sourcemodel.inside;
281-
source.avg.pow = zeros(size(source.pos, 1), 1);
282-
source.avg.pow(source.inside)=pbf;
290+
source.inside = reshape(source.inside, source.dim);
291+
source.pow = zeros(size(source.pos, 1), 1);
292+
source.pow(source.inside) = pbf;
293+
source.pow = reshape(source.pow, source.dim);
283294

284295
cfg = [];
285-
cfg.funparameter = 'avg.pow';
296+
cfg.funparameter = 'pow';
286297
cfg.method = 'slice';
287298
cfg.nslices = 10;
288299
cfg.funcolorlim = [0 0.2];
@@ -302,7 +313,7 @@ In the previous section we have seen that the depth bias is related to the fact
302313

303314
We will use the variables `sensordata`, `sourcemodel` and `L` that we also used in section 3. We also need the leadfields and source timecourses that we used for the simulations. If you don’t have these variables anymore in MATLAB memory, you should re-create them.
304315

305-
Now we will simulate data from a 'second’ condition (compared to the original variable sensordata), where the sources have the exact same locations and time courses, but the amplitude of two sources is decreased, and the amplitude of the other sources is increased, relative to the 'first’ condition.
316+
Now we will simulate data from a 'second’ condition (compared to the original variable sensordata), where the sources have the exact same locations and time courses, but the amplitude of two sources is decreased, and the amplitude of the two other sources is increased, relative to the 'first’ condition.
306317

307318
sensordata2 = 1.25 .* leadfield1*s1 + ...
308319
0.80 .* leadfield2*s2 + ...
@@ -317,14 +328,16 @@ We will now compute the spatial filters using the covariance estimated from the
317328

318329
> Use the `iCr2` variable to compute the spatial filters in the same way as in section 3.2. Call this variable `wbfr2`. Then, compute the source time courses for the conditions separately (hint: use `wbfr2*sensordata` and `wbfr2*sensordata2`), and compute the condition specific power estimate (as in section 4.1). Calle these estimates `pbfr1` and `pbfr2`.
319330
320-
We can now create a FieldTrip style source-structure, and store in the field `avg.pow` a measure of the difference between condition 1 and 2.
331+
We can now create a FieldTrip style source-structure, and store in the field `pow` a measure of the difference between condition 1 and 2.
321332

322333
source = [];
323334
source.pos = sourcemodel.pos;
324335
source.dim = sourcemodel.dim;
325336
source.inside = sourcemodel.inside;
326-
source.avg.pow = zeros(size(source.pos, 1), 1);
327-
source.avg.pow(source.inside) = (pbfr1-pbfr2)./(pbfr1+pbfr2);
337+
source.inside = reshape(source.inside, source.dim);
338+
source.pow = zeros(size(source.pos, 1), 1);
339+
source.pow(source.inside) = (pbfr1-pbfr2)./(pbfr1+pbfr2);
340+
source.pow = reshape(source.pow, source.dim);
328341

329342
cfg = [];
330343
cfg.funparameter = 'avg.pow';
@@ -347,8 +360,8 @@ First, we create some sensor data:
347360
leadfield2 = ni2_leadfield(sens, headmodel, [4.9 0 6.2 0 1 0]); % position 2352 in grid
348361

349362
% create the time course of activation
350-
[s1, t1] = ni2_activation('latency', .5, 'frequency', 10);
351-
[s2, t2] = ni2_activation('latency', .4, 'frequency', 10);
363+
[s1, t] = ni2_activation('latency', .5, 'frequency', 10);
364+
[s2, t] = ni2_activation('latency', .4, 'frequency', 10);
352365

353366
% create the sensor data
354367
sensordata = leadfield1*s1 + ...

0 commit comments

Comments
 (0)