/
ft_datatype_sens.m
595 lines (535 loc) · 27 KB
/
ft_datatype_sens.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
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
function [sens] = ft_datatype_sens(sens, varargin)
% FT_DATATYPE_SENS describes the FieldTrip structure that represents an MEG, EEG,
% sEEG, ECoG, or NIRS sensor array. This structure is commonly called "grad" for MEG,
% "elec" for EEG and intranial EEG, "opto" for NIRS, or in general "sens" if it could
% be any one.
%
% For all sensor types a distinction should be made between the channel (i.e. the
% output of the transducer that is A/D converted) and the sensor, which may have some
% spatial extent. For example in MEG gradiometers are comprised of multiple coils and
% with EEG you can have a bipolar channel, where the position of the channel can be
% represented as in between the position of the two electrodes.
%
% The structure for MEG gradiometers and/or magnetometers contains
% sens.label = Mx1 cell-array with channel labels
% sens.chanpos = Mx3 matrix with channel positions
% sens.chanori = Mx3 matrix with channel orientations, used for synthetic planar gradient computation
% sens.coilpos = Nx3 matrix with coil positions
% sens.coilori = Nx3 matrix with coil orientations
% sens.tra = MxN matrix to combine coils into channels
% sens.balance = structure containing info about the balancing, See FT_APPLY_MONTAGE
% and optionally
% sens.chanposold = Mx3 matrix with original channel positions (in case sens.chanpos has been updated to contain NaNs, e.g. after FT_COMPONENTANALYSIS)
% sens.chanoriold = Mx3 matrix with original channel orientations
% sens.labelold = Mx1 cell-array with original channel labels
%
% The structure for EEG, sEEG or ECoG channels contains
% sens.label = Mx1 cell-array with channel labels
% sens.chanpos = Mx3 matrix with channel positions (often the same as electrode positions)
% sens.elecpos = Nx3 matrix with electrode positions
% sens.tra = MxN matrix to combine electrodes into channels
% In case sens.tra is not present in the EEG sensor array, the channels
% are assumed to be average referenced.
%
% The structure for NIRS channels contains
% sens.label = Mx1 cell-array with channel labels
% sens.chanpos = Mx3 matrix with position of the channels (usually halfway the transmitter and receiver)
% sens.optopos = Nx3 matrix with the position of individual optodes
% sens.optotype = Nx1 cell-array with information about the type of optode (receiver or transmitter)
% sens.optolabel = Nx1 cell-array with optode labels
% sens.wavelength = 1xK vector of all wavelengths that were used
% sens.tra = MxN matrix that specifies for each of the M channels which of the N optodes transmits at which wavelength (positive integer from 1 to K), or receives (negative ingeger from 1 to K)
%
% The following fields apply to MEG, EEG, sEEG and ECoG
% sens.chantype = Mx1 cell-array with the type of the channel, see FT_CHANTYPE
% sens.chanunit = Mx1 cell-array with the units of the channel signal, e.g. 'V', 'fT' or 'T/cm', see FT_CHANUNIT
%
% Optional fields:
% type, unit, fid, chantype, chanunit, coordsys
%
% Historical fields:
% pnt, pos, ori, pnt1, pnt2, fiberpos, fibertype, fiberlabel, transceiver, transmits, laserstrength
%
% Revision history:
% (2020/latest) Updated the specification of the NIRS sensor definition.
% Dropped the laserstrength and renamed transmits into tra for consistency.
%
% (2019/latest) Updated the specification of the NIRS sensor definition.
% Use "opto" instead of "fibers", see http://bit.ly/33WaqWU for details.
%
% (2016) The chantype and chanunit have become required fields.
% Original channel details are specified with the suffix "old" rather than "org".
% All numeric values are represented in double precision.
% It is possible to convert the amplitude and distance units (e.g. from T to fT and
% from m to mm) and it is possible to express planar and axial gradiometer channels
% either in units of amplitude or in units of amplitude/distance (i.e. proper
% gradient).
%
% (2011v2) The chantype and chanunit have been added for MEG.
%
% (2011v1) To facilitate determining the position of channels (e.g. for plotting)
% in case of balanced MEG or bipolar EEG, an explicit distinction has been made
% between chanpos+chanori and coilpos+coilori (for MEG) and chanpos and elecpos
% (for EEG). The pnt and ori fields are removed.
%
% (2010) Added support for bipolar or otherwise more complex linear combinations
% of EEG electrodes using sens.tra, similar to MEG.
%
% (2009) Noise reduction has been added for MEG systems in the balance field.
%
% (2006) The optional fields sens.type and sens.unit were added.
%
% (2003) The initial version was defined, which looked like this for EEG
% sens.pnt = Mx3 matrix with electrode positions
% sens.label = Mx1 cell-array with channel labels
% and like this for MEG
% sens.pnt = Nx3 matrix with coil positions
% sens.ori = Nx3 matrix with coil orientations
% sens.tra = MxN matrix to combine coils into channels
% sens.label = Mx1 cell-array with channel labels
%
% See also FT_READ_SENS, FT_SENSTYPE, FT_CHANTYPE, FT_APPLY_MONTAGE, CTF2GRAD, FIF2GRAD,
% BTI2GRAD, YOKOGAWA2GRAD, ITAB2GRAD
% Copyright (C) 2011-2020, Robert Oostenveld & Jan-Mathijs Schoffelen
%
% This file is part of FieldTrip, see http://www.fieldtriptoolbox.org
% 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$
% undocumented options for the 2016 format and up
% amplitude = string, can be 'T' or 'fT'
% distance = string, can be 'm', 'cm' or 'mm'
% scaling = string, can be 'amplitude' or 'amplitude/distance'
% these are for speeding up subsequent calls with the same input arguments
persistent previous_argin previous_argout
current_argin = [{sens} varargin];
if isequal(current_argin, previous_argin)
% don't do the whole checking again, but return the previous output from cache
sens = previous_argout{1};
return
end
% get the optional input arguments, which should be specified as key-value pairs
version = ft_getopt(varargin, 'version', 'latest');
amplitude = ft_getopt(varargin, 'amplitude'); % should be 'V' 'uV' 'T' 'mT' 'uT' 'nT' 'pT' 'fT'
distance = ft_getopt(varargin, 'distance'); % should be 'm' 'dm' 'cm' 'mm'
scaling = ft_getopt(varargin, 'scaling'); % should be 'amplitude' or 'amplitude/distance', the default depends on the senstype
if ~isempty(amplitude) && ~any(strcmp(amplitude, {'V' 'uV' 'T' 'mT' 'uT' 'nT' 'pT' 'fT'}))
ft_error('unsupported unit of amplitude "%s"', amplitude);
end
if ~isempty(distance) && ~any(strcmp(distance, {'m' 'dm' 'cm' 'mm'}))
ft_error('unsupported unit of distance "%s"', distance);
end
if strcmp(version, 'latest')
version = '2020';
end
if isempty(sens)
return;
end
% this is needed further down
nchan = length(sens.label);
% these are used at multiple places, therefore we determine them only once
if isfield(sens, 'coilpos')
ismeg = true;
iseeg = true;
isnirs = false;
elseif isfield(sens, 'elecpos')
ismeg = false;
iseeg = true;
isnirs = false;
elseif isfield(sens, 'optopos')
ismeg = false;
iseeg = false;
isnirs = true;
else
% doing it this way takes a lot more CPU time
ismeg = ft_senstype(sens, 'meg');
iseeg = ft_senstype(sens, 'eeg');
isnirs = ft_senstype(sens, 'nirs');
end
switch version
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
case '2020'
% update it to the previous standard version
new_argin = ft_setopt(varargin, 'version', '2019');
sens = ft_datatype_sens(sens, new_argin{:});
if isfield(sens, 'coordsys')
sens = fixcoordsys(sens);
end
if isnirs
sens = renamefields(sens, 'transmits', 'tra'); % this makes it more consistent with EEG and MEG
sens = removefields(sens, {'laserstrength'});
if isfield(sens, 'optotype') && isfield(sens, 'tra')
% the read_artinis_oxy3 file returns the wrong sign for the receivers and transmitters
% but since it is p-code, it cannot be fixed
tmp = sens.tra(:, strcmp(sens.optotype, 'transmitter')); correctT = all(tmp(:)>=0);
tmp = sens.tra(:, strcmp(sens.optotype, 'receiver' )); correctR = all(tmp(:)<=0);
if ~correctT
ft_warning('flipping sign for transmitters');
sens.tra(:, strcmp(sens.optotype, 'transmitter')) = -sens.tra(:, strcmp(sens.optotype, 'transmitter'));
end
if ~correctR
ft_warning('flipping sign for receivers');
sens.tra(:, strcmp(sens.optotype, 'receiver')) = -sens.tra(:, strcmp(sens.optotype, 'receiver'));
end
end
end % ifnirs
% ensure internal consistency
nchan = numel(sens.label);
if isfield(sens, 'chanpos')
assert(size(sens.chanpos, 1)==nchan, 'sensor structure is inconsistent');
end
if isfield(sens, 'chanori')
assert(size(sens.chanori, 1)==nchan, 'sensor structure is inconsistent');
end
if isfield(sens, 'tra')
assert(size(sens.tra, 1)==nchan);
if isfield(sens, 'elecpos')
assert(size(sens.tra,2)==size(sens.elecpos,1), 'sensor structure is inconsistent')
elseif isfield(sens, 'optopos')
assert(size(sens.tra,2)==size(sens.optopos,1), 'sensor structure is inconsistent')
elseif isfield(sens, 'coilpos')
assert(size(sens.tra,2)==size(sens.coilpos,1), 'sensor structure is inconsistent')
end
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
case '2019'
% update it to the previous standard version
new_argin = ft_setopt(varargin, 'version', '2016');
sens = ft_datatype_sens(sens, new_argin{:});
if isnirs
% rename some NIRS field names
sens = renamefields(sens, 'fiberpos', 'optopos');
sens = renamefields(sens, 'fibertype', 'optotype');
sens = renamefields(sens, 'fiberlabel', 'optolabel');
sens = renamefields(sens, 'transceiver', 'transmits');
% these might be present due to the reading functions but are not part of the user/technical documentation yet, so better not include them for now
% especially the chanunit field needs some careful thought when converting between optical densities and chromophore concentrations.
sens = removefields(sens, {'chantype', 'chanunit'});
end
% ensure all positions are represented as 3D, not 2D
fn = {'chanpos', 'optopos', 'elecpos', 'coilpos'};
for i=1:numel(fn)
if isfield(sens, fn{i}) && size(sens.(fn{i}),2)==2
ft_notice('converting %s from 2D to 3D', fn{i});
sens.(fn{i})(:,3) = 0;
end
end % for
if isfield(sens, 'optolabel')
if length(sens.optolabel)~=length(unique(sens.optolabel))
ft_warning('non-unique optode labels detected');
end
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
case '2016'
% update it to the previous standard version
new_argin = ft_setopt(varargin, 'version', '2011v2');
sens = ft_datatype_sens(sens, new_argin{:});
% rename from org to old (reverse = false)
sens = fixoldorg(sens, false);
% ensure that all numbers are represented in double precision
% this only affects the top-level fields and does not recurse
sens = ft_struct2double(sens, 1);
% in version 2011v2 this was optional, now it is required
if ~isfield(sens, 'chantype') || all(strcmp(sens.chantype, 'unknown'))
sens.chantype = ft_chantype(sens);
end
% in version 2011v2 this was optional, now it is required
if ~isfield(sens, 'chanunit') || all(strcmp(sens.chanunit, 'unknown'))
sens.chanunit = ft_chanunit(sens);
end
if ~isempty(distance)
% update the units of distance, this also updates the tra matrix
sens = ft_convert_units(sens, distance);
else
% determine the default, this may be needed to set the scaling
distance = sens.unit;
end
if ~isempty(amplitude) && isfield(sens, 'tra')
% update the tra matrix for the units of amplitude, this ensures that
% the leadfield values remain consistent with the units
for i=1:nchan
if ~isempty(regexp(sens.chanunit{i}, 'm$', 'once'))
% this channel is expressed as amplitude per distance
sens.tra(i,:) = sens.tra(i,:) * ft_scalingfactor(sens.chanunit{i}, [amplitude '/' distance]);
sens.chanunit{i} = [amplitude '/' distance];
elseif ~isempty(regexp(sens.chanunit{i}, '[T|V]$', 'once'))
% this channel is expressed as amplitude
sens.tra(i,:) = sens.tra(i,:) * ft_scalingfactor(sens.chanunit{i}, amplitude);
sens.chanunit{i} = amplitude;
else
ft_error('unexpected channel unit "%s" in channel %d', sens.chanunit{i}, i);
end
end
else
% determine the default amplityde, this may be needed to set the scaling
if any(~cellfun(@isempty, regexp(sens.chanunit, '^T')))
% one of the channel units starts with T
amplitude = 'T';
elseif any(~cellfun(@isempty, regexp(sens.chanunit, '^fT')))
% one of the channel units starts with fT
amplitude = 'fT';
elseif any(~cellfun(@isempty, regexp(sens.chanunit, '^V')))
% one of the channel units starts with V
amplitude = 'V';
elseif any(~cellfun(@isempty, regexp(sens.chanunit, '^uV')))
% one of the channel units starts with uV
amplitude = 'uV';
else
% this unknown amplitude will cause a problem if the scaling needs to be changed between amplitude and amplitude/distance
amplitude = 'unknown';
end
end
% perform some sanity checks
if ismeg
sel_m = ~cellfun(@isempty, regexp(sens.chanunit, '/m$'));
sel_dm = ~cellfun(@isempty, regexp(sens.chanunit, '/dm$'));
sel_cm = ~cellfun(@isempty, regexp(sens.chanunit, '/cm$'));
sel_mm = ~cellfun(@isempty, regexp(sens.chanunit, '/mm$'));
if strcmp(sens.unit, 'm') && (any(sel_dm) || any(sel_cm) || any(sel_mm))
ft_error('inconsistent units in input gradiometer');
elseif strcmp(sens.unit, 'dm') && (any(sel_m) || any(sel_cm) || any(sel_mm))
ft_error('inconsistent units in input gradiometer');
elseif strcmp(sens.unit, 'cm') && (any(sel_m) || any(sel_dm) || any(sel_mm))
ft_error('inconsistent units in input gradiometer');
elseif strcmp(sens.unit, 'mm') && (any(sel_m) || any(sel_dm) || any(sel_cm))
ft_error('inconsistent units in input gradiometer');
end
if ~strcmp(amplitude, 'unknown') && ~strcmp(distance, 'unknown')
% the default should be "amplitude/distance" for neuromag and "amplitude" for all others
if isempty(scaling)
if ft_senstype(sens, 'neuromag') && ~any(contains(sens.chanunit, '/'))
ft_warning('assuming that the default scaling should be amplitude/distance rather than amplitude');
scaling = 'amplitude/distance';
elseif ft_senstype(sens, 'yokogawa440') && any(contains(sens.chanunit, '/'))
ft_warning('assuming that the default scaling should be amplitude rather than amplitude/distance');
scaling = 'amplitude';
end
end
% update the gradiometer scaling
if strcmp(scaling, 'amplitude') && isfield(sens, 'tra')
for i=1:nchan
if strcmp(sens.chanunit{i}, [amplitude '/' distance])
% this channel is expressed as amplitude per distance
coil = find(abs(sens.tra(i,:))~=0);
if length(coil)~=2
ft_error('unexpected number of coils contributing to channel %d', i);
end
baseline = norm(sens.coilpos(coil(1),:) - sens.coilpos(coil(2),:));
sens.tra(i,:) = sens.tra(i,:)*baseline; % scale with the baseline distance
sens.chanunit{i} = amplitude;
elseif strcmp(sens.chanunit{i}, amplitude)
% no conversion needed
elseif isfield(sens, 'balance') && strcmp(sens.balance.current, 'comp')
% no conversion needed
elseif isfield(sens, 'balance') && strcmp(sens.balance.current, 'planar')
% no conversion needed
else
% see http://bugzilla.fieldtriptoolbox.org/show_bug.cgi?id=3144
ft_warning('unexpected channel unit "%s" in channel %d', sens.chanunit{i}, i);
end % if
end % for nchan
elseif strcmp(scaling, 'amplitude/distance') && isfield(sens, 'tra')
for i=1:nchan
if strcmp(sens.chanunit{i}, amplitude)
% this channel is expressed as amplitude
coil = find(abs(sens.tra(i,:))~=0);
if length(coil)==1 || strcmp(sens.chantype{i}, 'megmag')
% this is a magnetometer channel, no conversion needed
continue
elseif length(coil)~=2
ft_error('unexpected number of coils (%d) contributing to channel %s (%d)', length(coil), sens.label{i}, i);
end
baseline = norm(sens.coilpos(coil(1),:) - sens.coilpos(coil(2),:));
sens.tra(i,:) = sens.tra(i,:)/baseline; % scale with the baseline distance
sens.chanunit{i} = [amplitude '/' distance];
elseif strcmp(sens.chanunit{i}, [amplitude '/' distance])
% no conversion needed
else
% see http://bugzilla.fieldtriptoolbox.org/show_bug.cgi?id=3144
ft_warning('unexpected channel unit "%s" in channel %d', sens.chanunit{i}, i);
end % if
end % for nchan
end % if strcmp scaling
end % if amplitude and scaling are not unknown
else
sel_m = ~cellfun(@isempty, regexp(sens.chanunit, '/m$'));
sel_dm = ~cellfun(@isempty, regexp(sens.chanunit, '/dm$'));
sel_cm = ~cellfun(@isempty, regexp(sens.chanunit, '/cm$'));
sel_mm = ~cellfun(@isempty, regexp(sens.chanunit, '/mm$'));
if any(sel_m | sel_dm | sel_cm | sel_mm)
ft_error('scaling of amplitude/distance has not been considered yet for EEG');
end
end % if iseeg or ismeg
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
case '2011v2'
% rename from old to org (reverse = true)
sens = fixoldorg(sens, true);
if ~isempty(amplitude) || ~isempty(distance) || ~isempty(scaling)
ft_warning('amplitude, distance and scaling are not supported for version "%s"', version);
end
% This speeds up subsequent calls to ft_senstype and channelposition.
% However, if it is not more precise than MEG or EEG, don't keep it in
% the output (see further down).
if ~isfield(sens, 'type')
sens.type = ft_senstype(sens);
end
if isfield(sens, 'pnt')
if ismeg
% sensor description is a MEG sensor-array, containing oriented coils
sens.coilpos = sens.pnt; sens = rmfield(sens, 'pnt');
sens.coilori = sens.ori; sens = rmfield(sens, 'ori');
else
% sensor description is something else, EEG/ECoG/sEEG, etc
sens.elecpos = sens.pnt; sens = rmfield(sens, 'pnt');
end
end
if isfield(sens, 'pos')
if ismeg
% sensor description is a MEG sensor-array, containing oriented coils
sens.coilpos = sens.pos; sens = rmfield(sens, 'pos');
sens.coilori = sens.ori; sens = rmfield(sens, 'ori');
else
% sensor description is something else, EEG/ECoG/sEEG, etc
sens.elecpos = sens.pos; sens = rmfield(sens, 'pos');
end
end
if ~isfield(sens, 'chanpos')
if ismeg
% sensor description is a MEG sensor-array, containing oriented coils
[chanpos, chanori, lab] = channelposition(sens);
% the channel order can be different in the two representations
[selsens, selpos] = match_str(sens.label, lab);
sens.chanpos = nan(length(sens.label), 3);
sens.chanori = nan(length(sens.label), 3);
% insert the determined position/orientation on the appropriate rows
sens.chanpos(selsens,:) = chanpos(selpos,:);
sens.chanori(selsens,:) = chanori(selpos,:);
if length(selsens)~=length(sens.label)
ft_warning('cannot determine the position and orientation for all channels');
end
else
% sensor description is something else, EEG/ECoG etc
% note that chanori will be all NaNs
[chanpos, chanori, lab] = channelposition(sens);
% the channel order can be different in the two representations
[selsens, selpos] = match_str(sens.label, lab);
sens.chanpos = nan(length(sens.label), 3);
% insert the determined position/orientation on the appropriate rows
sens.chanpos(selsens,:) = chanpos(selpos,:);
if length(selsens)~=length(sens.label)
ft_warning('cannot determine the position and orientation for all channels');
end
end
end
if ~isfield(sens, 'chantype') || all(strcmp(sens.chantype, 'unknown'))
if ismeg
sens.chantype = ft_chantype(sens);
else
% for EEG it is not required
end
end
if ~isfield(sens, 'unit')
% this should be done prior to calling ft_chanunit, since ft_chanunit uses this for planar neuromag channels
sens = ft_determine_units(sens);
end
if ~isfield(sens, 'chanunit') || all(strcmp(sens.chanunit, 'unknown'))
if ismeg
sens.chanunit = ft_chanunit(sens);
else
% for EEG it is not required
end
end
if any(strcmp(sens.type, {'meg', 'eeg', 'magnetometer', 'electrode', 'unknown'}))
% this is not sufficiently informative, so better remove it
% see also http://bugzilla.fieldtriptoolbox.org/show_bug.cgi?id=1806
sens = rmfield(sens, 'type');
end
if size(sens.chanpos,1)~=length(sens.label) || ...
isfield(sens, 'tra') && size(sens.tra,1)~=length(sens.label) || ...
isfield(sens, 'tra') && isfield(sens, 'elecpos') && size(sens.tra,2)~=size(sens.elecpos,1) || ...
isfield(sens, 'tra') && isfield(sens, 'coilpos') && size(sens.tra,2)~=size(sens.coilpos,1) || ...
isfield(sens, 'tra') && isfield(sens, 'coilori') && size(sens.tra,2)~=size(sens.coilori,1) || ...
isfield(sens, 'chanpos') && size(sens.chanpos,1)~=length(sens.label) || ...
isfield(sens, 'chanori') && size(sens.chanori,1)~=length(sens.label)
ft_error('inconsistent number of channels in sensor description');
end
if ismeg
% ensure that the magnetometer/gradiometer balancing is specified
if ~isfield(sens, 'balance') || ~isfield(sens.balance, 'current')
sens.balance.current = 'none';
end
% try to add the chantype and chanunit to the CTF G1BR montage
if isfield(sens, 'balance') && isfield(sens.balance, 'G1BR') && ~isfield(sens.balance.G1BR, 'chantype')
sens.balance.G1BR.chantypeorg = repmat({'unknown'}, size(sens.balance.G1BR.labelorg));
sens.balance.G1BR.chanunitorg = repmat({'unknown'}, size(sens.balance.G1BR.labelorg));
sens.balance.G1BR.chantypenew = repmat({'unknown'}, size(sens.balance.G1BR.labelnew));
sens.balance.G1BR.chanunitnew = repmat({'unknown'}, size(sens.balance.G1BR.labelnew));
% the synthetic gradient montage does not fundamentally change the chantype or chanunit
[sel1, sel2] = match_str(sens.balance.G1BR.labelorg, sens.label);
sens.balance.G1BR.chantypeorg(sel1) = sens.chantype(sel2);
sens.balance.G1BR.chanunitorg(sel1) = sens.chanunit(sel2);
[sel1, sel2] = match_str(sens.balance.G1BR.labelnew, sens.label);
sens.balance.G1BR.chantypenew(sel1) = sens.chantype(sel2);
sens.balance.G1BR.chanunitnew(sel1) = sens.chanunit(sel2);
end
% idem for G2BR
if isfield(sens, 'balance') && isfield(sens.balance, 'G2BR') && ~isfield(sens.balance.G2BR, 'chantype')
sens.balance.G2BR.chantypeorg = repmat({'unknown'}, size(sens.balance.G2BR.labelorg));
sens.balance.G2BR.chanunitorg = repmat({'unknown'}, size(sens.balance.G2BR.labelorg));
sens.balance.G2BR.chantypenew = repmat({'unknown'}, size(sens.balance.G2BR.labelnew));
sens.balance.G2BR.chanunitnew = repmat({'unknown'}, size(sens.balance.G2BR.labelnew));
% the synthetic gradient montage does not fundamentally change the chantype or chanunit
[sel1, sel2] = match_str(sens.balance.G2BR.labelorg, sens.label);
sens.balance.G2BR.chantypeorg(sel1) = sens.chantype(sel2);
sens.balance.G2BR.chanunitorg(sel1) = sens.chanunit(sel2);
[sel1, sel2] = match_str(sens.balance.G2BR.labelnew, sens.label);
sens.balance.G2BR.chantypenew(sel1) = sens.chantype(sel2);
sens.balance.G2BR.chanunitnew(sel1) = sens.chanunit(sel2);
end
% idem for G3BR
if isfield(sens, 'balance') && isfield(sens.balance, 'G3BR') && ~isfield(sens.balance.G3BR, 'chantype')
sens.balance.G3BR.chantypeorg = repmat({'unknown'}, size(sens.balance.G3BR.labelorg));
sens.balance.G3BR.chanunitorg = repmat({'unknown'}, size(sens.balance.G3BR.labelorg));
sens.balance.G3BR.chantypenew = repmat({'unknown'}, size(sens.balance.G3BR.labelnew));
sens.balance.G3BR.chanunitnew = repmat({'unknown'}, size(sens.balance.G3BR.labelnew));
% the synthetic gradient montage does not fundamentally change the chantype or chanunit
[sel1, sel2] = match_str(sens.balance.G3BR.labelorg, sens.label);
sens.balance.G3BR.chantypeorg(sel1) = sens.chantype(sel2);
sens.balance.G3BR.chanunitorg(sel1) = sens.chanunit(sel2);
[sel1, sel2] = match_str(sens.balance.G3BR.labelnew, sens.label);
sens.balance.G3BR.chantypenew(sel1) = sens.chantype(sel2);
sens.balance.G3BR.chanunitnew(sel1) = sens.chanunit(sel2);
end
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
otherwise
ft_error('converting to version %s is not supported', version);
end % switch
% this makes the display with the "disp" command look better
sens = sortfieldnames(sens);
% remember the current input and output arguments, so that they can be
% reused on a subsequent call in case the same input argument is given
current_argout = {sens};
previous_argin = current_argin;
previous_argout = current_argout;
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% SUBFUNCTION
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function b = sortfieldnames(a)
fn = sort(fieldnames(a));
for i=1:numel(fn)
b.(fn{i}) = a.(fn{i});
end