forked from fieldtrip/fieldtrip
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathft_prepare_neighbours.m
340 lines (306 loc) · 12.7 KB
/
ft_prepare_neighbours.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
function [neighbours, cfg] = ft_prepare_neighbours(cfg, data)
% FT_PREPARE_NEIGHBOURS finds the neighbours of the channels based on three
% different methods. Using the 'distance'-method, prepare_neighbours is
% based on a minimum neighbourhood distance (in cfg.neighbourdist). The
% 'triangulation'-method calculates a triangulation based on a
% two-dimenstional projection of the sensor position. The 'template'-method
% loads a default template for the given data type. prepare_neighbours
% should be verified using cfg.feedback ='yes' or by calling
% ft_neighbourplot
%
% The positions of the channel are specified in a gradiometer or electrode configuration or
% from a layout. The sensor configuration can be passed into this function in three ways:
% (1) in a configuration field,
% (2) in a file whose name is passed in a configuration field, and that can be imported using FT_READ_SENS, or
% (3) in a data field.
%
% Use as
% neighbours = ft_prepare_neighbours(cfg, data)
%
% The configuration can contain
% cfg.method = 'distance', 'triangulation' or 'template'
% cfg.neighbourdist = number, maximum distance between neighbouring sensors (only for 'distance')
% cfg.template = name of the template file, e.g. CTF275_neighb.mat
% cfg.layout = filename of the layout, see FT_PREPARE_LAYOUT
% cfg.channel = channels for which neighbours should be found
% cfg.feedback = 'yes' or 'no' (default = 'no')
%
% The EEG or MEG sensor positions can be present in the data or can be specified as
% cfg.elec = structure with electrode positions, see FT_DATATYPE_SENS
% cfg.grad = structure with gradiometer definition, see FT_DATATYPE_SENS
% cfg.elecfile = name of file containing the electrode positions, see FT_READ_SENS
% cfg.gradfile = name of file containing the gradiometer definition, see FT_READ_SENS
%
% The output is an array of structures with the "neighbours" which is
% structured like this:
% neighbours(1).label = 'Fz';
% neighbours(1).neighblabel = {'Cz', 'F3', 'F3A', 'FzA', 'F4A', 'F4'};
% neighbours(2).label = 'Cz';
% neighbours(2).neighblabel = {'Fz', 'F4', 'RT', 'RTP', 'P4', 'Pz', 'P3', 'LTP', 'LT', 'F3'};
% neighbours(3).label = 'Pz';
% neighbours(3).neighblabel = {'Cz', 'P4', 'P4P', 'Oz', 'P3P', 'P3'};
% etc.
% Note that a channel is not considered to be a neighbour of itself.
%
% See also FT_NEIGHBOURPLOT
% Copyright (C) 2006-2011, Eric Maris, Jorn M. Horschig, Robert Oostenveld
%
% This file is part of FieldTrip, see http://www.ru.nl/neuroimaging/fieldtrip
% for the documentation and details.
%
% FieldTrip is free software: you can redistribute it and/or modify
% it under the terms of the GNU General Public License as published by
% the Free Software Foundation, either version 3 of the License, or
% (at your option) any later version.
%
% FieldTrip is distributed in the hope that it will be useful,
% but WITHOUT ANY WARRANTY; without even the implied warranty of
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
% GNU General Public License for more details.
%
% You should have received a copy of the GNU General Public License
% along with FieldTrip. If not, see <http://www.gnu.org/licenses/>.
%
% $Id$
revision = '$Id$';
% do the general setup of the function
ft_defaults
ft_preamble init
ft_preamble provenance
ft_preamble trackconfig
ft_preamble debug
% the abort variable is set to true or false in ft_preamble_init
if abort
return
end
% check if the input cfg is valid for this function
cfg = ft_checkconfig(cfg, 'required', {'method'});
% set the defaults
cfg.feedback = ft_getopt(cfg, 'feedback', 'no');
cfg.channel = ft_getopt(cfg, 'channel', 'all');
hasdata = nargin>1;
if hasdata
% check if the input data is valid for this function
data = ft_checkdata(data);
end
if strcmp(cfg.method, 'template')
neighbours = [];
fprintf('Trying to load sensor neighbours from a template\n');
% determine from where to load the neighbour template
if ~isfield(cfg, 'template')
% if data has been put in, try to estimate the sensor type
if hasdata
fprintf('Estimating sensor type of data to determine the layout filename\n');
senstype = ft_senstype(data.label);
fprintf('Data is of sensor type ''%s''\n', senstype);
if ~exist([senstype '_neighb.mat'], 'file')
if exist([senstype '.lay'], 'file')
cfg.layout = [senstype '.lay'];
else
fprintf('Name of sensor type does not match name of layout- and template-file\n');
end
else
cfg.template = [senstype '_neighb.mat'];
end
end
end
% if that failed
if ~isfield(cfg, 'template')
% check whether a layout can be used
if ~isfield(cfg, 'layout')
% error if that fails as well
error('You need to define a template or layout or give data as an input argument when ft_prepare_neighbours is called with cfg.method=''template''');
end
fprintf('Using the 2-D layout filename to determine the template filename\n');
cfg.template = [strtok(cfg.layout, '.') '_neighb.mat'];
end
% adjust filename
if ~exist(cfg.template, 'file')
cfg.template = lower(cfg.template);
end
% add necessary extensions
if numel(cfg.template) < 4 || ~isequal(cfg.template(end-3:end), '.mat')
if numel(cfg.template) < 7 || ~isequal(cfg.template(end-6:end), '_neighb')
cfg.template = [cfg.template, '_neighb'];
end
cfg.template = [cfg.template, '.mat'];
end
% check for existence
if ~exist(cfg.template, 'file')
error('Template file could not be found - please check spelling or see http://fieldtrip.fcdonders.nl/faq/how_can_i_define_my_own_neighbourhood_template (please consider sharing it with others via the FT mailing list)');
end
load(cfg.template);
fprintf('Successfully loaded neighbour structure from %s\n', cfg.template);
else
% get the the grad or elec if not present in the data
if hasdata
sens = ft_fetch_sens(cfg, data);
else
sens = ft_fetch_sens(cfg);
end
if strcmp(ft_senstype(sens), 'neuromag306')
warning('Neuromagr06 system detected - be aware of different sensor types, see http://fieldtrip.fcdonders.nl/faq/why_are_there_multiple_neighbour_templates_for_the_neuromag306_system');
end
chanpos = sens.chanpos;
label = sens.label;
if nargin > 1
% remove channels that are not in data
[dataidx sensidx] = match_str(data.label, label);
chanpos = chanpos(sensidx, :);
label = label(sensidx);
end
if ~strcmp(cfg.channel, 'all')
desired = ft_channelselection(cfg.channel, label);
[sensidx] = match_str(label, desired);
chanpos = chanpos(sensidx, :);
label = label(sensidx);
end
switch lower(cfg.method)
case 'distance'
% use a smart default for the distance
if ~isfield(cfg, 'neighbourdist')
sens = ft_checkdata(sens, 'hasunit', 'yes');
if isfield(sens, 'unit') && strcmp(sens.unit, 'm')
cfg.neighbourdist = 0.04;
elseif isfield(sens, 'unit') && strcmp(sens.unit, 'dm')
cfg.neighbourdist = 0.4;
elseif isfield(sens, 'unit') && strcmp(sens.unit, 'cm')
cfg.neighbourdist = 4;
elseif isfield(sens, 'unit') && strcmp(sens.unit, 'mm')
cfg.neighbourdist = 40;
else
% don't provide a default in case the dimensions of the sensor array are unknown
error('Sensor distance is measured in an unknown unit type');
end
fprintf('using a distance threshold of %g\n', cfg.neighbourdist);
end
neighbours = compneighbstructfromgradelec(chanpos, label, cfg.neighbourdist);
case {'triangulation', 'tri'} % the latter for reasons of simplicity
if size(chanpos, 2)==2 || all(chanpos(:,3)==0)
% the sensor positions are already on a 2D plane
prj = chanpos(:,1:2);
else
% project sensor on a 2D plane
prj = elproj(chanpos);
end
% make a 2d delaunay triangulation of the projected points
tri = delaunay(prj(:,1), prj(:,2));
tri_x = delaunay(prj(:,1)./2, prj(:,2));
tri_y = delaunay(prj(:,1), prj(:,2)./2);
tri = [tri; tri_x; tri_y];
neighbours = compneighbstructfromtri(chanpos, label, tri);
otherwise
error('Method ''%s'' not known', cfg.method);
end
end
% removed as from Nov 09 2011 - hope there are no problems with this
% if iscell(neighbours)
% warning('Neighbourstructure is in old format - converting to structure array');
% neighbours = fixneighbours(neighbours);
% end
% only select those channels that are in the data
neighb_chans = {neighbours(:).label};
if isfield(cfg, 'channel') && ~isempty(cfg.channel)
if hasdata
desired = ft_channelselection(cfg.channel, data.label);
else
desired = ft_channelselection(cfg.channel, neighb_chans);
end
elseif (hasdata)
desired = data.label;
else
desired = neighb_chans;
end
% in any case remove SCALE and COMNT
desired = ft_channelselection({'all', '-SCALE', '-COMNT'}, desired);
neighb_idx = ismember(neighb_chans, desired);
neighbours = neighbours(neighb_idx);
k = 0;
for i=1:length(neighbours)
if isempty(neighbours(i).neighblabel)
warning('FIELDTRIP:NoNeighboursFound', 'no neighbours found for %s\n', neighbours(i).label);
% JMH: I removed this in Feb 2013 - this is handled above now
% note however that in case of using a template, this function behaves
% differently now (neighbourschans can still be channels not in
% cfg.channel)
%else % only selected desired channels
% neighbours(i).neighblabel = neighbours(i).neighblabel(ismember(neighbours(i).neighblabel, desired));
end
k = k + length(neighbours(i).neighblabel);
end
if k==0, error('No neighbours were found!'); end;
fprintf('there are on average %.1f neighbours per channel\n', k/length(neighbours));
if strcmp(cfg.feedback, 'yes')
% give some graphical feedback
cfg.neighbours = neighbours;
if hasdata
ft_neighbourplot(cfg, data);
else
ft_neighbourplot(cfg);
end
end
% do the general cleanup and bookkeeping at the end of the function
ft_postamble debug
ft_postamble trackconfig
ft_postamble provenance
if hasdata
ft_postamble previous data
end
ft_postamble history neighbours
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% SUBFUNCTION that compute the neighbourhood geometry from the
% gradiometer/electrode positions
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function [neighbours]=compneighbstructfromgradelec(chanpos, label, neighbourdist)
nsensors = length(label);
% compute the distance between all sensors
dist = zeros(nsensors,nsensors);
for i=1:nsensors
dist(i,:) = sqrt(sum((chanpos(1:nsensors,:) - repmat(chanpos(i,:), nsensors, 1)).^2,2))';
end;
% find the neighbouring electrodes based on distance
% later we have to restrict the neighbouring electrodes to those actually selected in the dataset
channeighbstructmat = (dist<neighbourdist);
% electrode istelf is not a neighbour
channeighbstructmat = (channeighbstructmat .* ~eye(nsensors));
% construct a structured cell array with all neighbours
neighbours=struct;
for i=1:nsensors
neighbours(i).label = label{i};
neighbours(i).neighblabel = label(find(channeighbstructmat(i,:)));
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% SUBFUNCTION that computes the neighbourhood geometry from the
% triangulation
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function [neighbours]=compneighbstructfromtri(chanpos, label, tri)
nsensors = length(label);
channeighbstructmat = zeros(nsensors,nsensors);
% mark neighbours according to triangulation
for i=1:size(tri, 1)
channeighbstructmat(tri(i, 1), tri(i, 2)) = 1;
channeighbstructmat(tri(i, 1), tri(i, 3)) = 1;
channeighbstructmat(tri(i, 2), tri(i, 1)) = 1;
channeighbstructmat(tri(i, 3), tri(i, 1)) = 1;
channeighbstructmat(tri(i, 2), tri(i, 3)) = 1;
channeighbstructmat(tri(i, 3), tri(i, 2)) = 1;
end
% construct a structured cell array with all neighbours
neighbours=struct;
alldist = [];
for i=1:nsensors
neighbours(i).label = label{i};
neighbidx = find(channeighbstructmat(i,:));
neighbours(i).dist = sqrt(sum((repmat(chanpos(i, :), numel(neighbidx), 1) - chanpos(neighbidx, :)).^2, 2));
alldist = [alldist; neighbours(i).dist];
neighbours(i).neighblabel = label(neighbidx);
end
% remove neighbouring channels that are too far away (imporntant e.g. in
% case of missing sensors)
neighbdist = mean(alldist)+3*std(alldist);
for i=1:nsensors
idx = neighbours(i).dist > neighbdist;
neighbours(i).dist(idx) = [];
neighbours(i).neighblabel(idx) = [];
end
neighbours = rmfield(neighbours, 'dist');