function nbrhood=cosmo_meeg_chan_neighborhood(ds, varargin)
% determine neighborhood of channels in MEEG dataset
%
% nbrhood=cosmo_meeg_chan_neighborhood(ds, ...)
%
% Inputs:
% ds MEEG dataset struct.
% ft_nbrs Optional Fieldtrip-like neighborhood struct with
% fields .label and .neighblabel such as produced by
% FieldTrip's ft_prepare_neighbours. In this case none
% of the other options mentioned below can be
% provided; and the neighborhood structure is based
% on the neighbors from ft_nbrs. The struct ft_nbrs
% must be structured as in this simple example:
% ft_nbrs(1).name='ch1'
% ft_nbrs(1).neighblabel={'ch2','ch6','ch9'}
% ft_nbrs(2).name='ch2'
% ft_nbrs(2).neighblabel={'ch1','ch6'}
% ft_nbrs(3).name='ch6'
% ft_nbrs(3).neighblabel={'ch1','ch2'}
% ft_nbrs(4).name='ch9'
% ft_nbrs(4).neighblabel={'ch1'}
% 'label', lab Optional labels to return in output, one of:
% 'layout' : determine neighbors based on layout
% associated with ds (default). All
% labels in the layout are used as
% center labels.
% 'dataset' : determine neighbors based on labels
% present in ds. Only labels present in
% ds are used as center labels
% {x1,...,xn} : use labels x1 ... xn
% 'chantype', tp (optional) channel type of neighbors, can be one of
% 'eeg', 'meg_planar', 'meg_axial', or
% 'meg_combined_from_planar'.
% Use 'all' to use all channel types associated with
% lab, and 'all_combined' to use
% 'meg_combined_from_planar' with all other channel
% types in ds except for 'meg_planar'.
% If there is only one channel type associated with
% lab, then this argument is not required.
% 'radius', r } select neighbors either within radius r, grow
% 'count', c } the radius to get neighbors at c locations,
% 'delaunay', true } or use Delaunay triangulation to find direct
% } neighbors for each channel.
% } These three options are mutually exclusive
%
% Output:
% nbrhood struct with fields:
% .neighbors Kx1 cell with feature indices of neighbors of
% k-th channel
% .fa.chan channel indices, equal to 1:K
% .a.fdim.values set to a cell with as only element a cell with
% center channel labels
% .a.fdim.labels set to {'chan'}
% .origin.fa } set to the corresponding values from
% .origin.a } the input dataset, ds.fa and ds.a
%
% Examples:
% % (This example requires FieldTrip)
% cosmo_skip_test_if_no_external('fieldtrip');
% %
% % get neighbors at 4 neighboring sensor location for
% % planar neuromag306 channels
% ds=cosmo_synthetic_dataset('type','meeg','size','big');
% nbrhood=cosmo_meeg_chan_neighborhood(ds,...
% 'chantype','meg_planar','count',4);
% cosmo_disp(nbrhood,'edgeitems',1);
% %|| .neighbors
% %|| { [ 2 ... 2e+03 ]@1x28
% %|| :
% %|| [ 149 ... 2.14e+03 ]@1x28 }@204x1
% %|| .fa
% %|| .chan
% %|| [ 1 ... 204 ]@1x204
% %|| .a
% %|| .fdim
% %|| .labels
% %|| { 'chan' }
% %|| .values
% %|| { { 'MEG0113' ... 'MEG2643' }@1x204 }
% %|| .meeg
% %|| .samples_type
% %|| 'timelock'
% %|| .samples_field
% %|| 'trial'
% %|| .samples_label
% %|| 'rpt'
% %|| .origin
% %|| .fa
% %|| .chan
% %|| [ 1 ... 306 ]@1x2142
% %|| .time
% %|| [ 1 ... 7 ]@1x2142
% %|| .a
% %|| .fdim
% %|| .labels
% %|| { 'chan'
% %|| 'time' }
% %|| .values
% %|| { { <char>@1x7 ... <char>@1x7 }@1x306
% %|| [ -0.2 ... 0.1 ]@1x7 }
% %|| .meeg
% %|| .samples_type
% %|| 'timelock'
% %|| .samples_field
% %|| 'trial'
% %|| .samples_label
% %|| 'rpt'
%
% % (This example requires FieldTrip)
% cosmo_skip_test_if_no_external('fieldtrip');
% %
% % get neighbors with radius of .1 for
% % planar neuromag306 channels, but with the center labels
% % the set of combined planar channels
% % (there are 8 channels in the .neighblabel fields, because
% % there are two planar channels per combined channel)
% ds=cosmo_synthetic_dataset('type','meeg','size','big');
% nbrhood=cosmo_meeg_chan_neighborhood(ds,...
% 'chantype','meg_combined_from_planar','radius',.1);
% cosmo_disp(nbrhood,'edgeitems',1);
% %|| .neighbors
% %|| { [ 2 ... 2e+03 ]@1x42
% %|| :
% %|| [ 149 ... 2.14e+03 ]@1x56 }@102x1
% %|| .fa
% %|| .chan
% %|| [ 1 ... 102 ]@1x102
% %|| .a
% %|| .fdim
% %|| .labels
% %|| { 'chan' }
% %|| .values
% %|| { { 'MEG0112+0113' ... 'MEG2642+2643' }@1x102 }
% %|| .meeg
% %|| .samples_type
% %|| 'timelock'
% %|| .samples_field
% %|| 'trial'
% %|| .samples_label
% %|| 'rpt'
% %|| .origin
% %|| .fa
% %|| .chan
% %|| [ 1 ... 306 ]@1x2142
% %|| .time
% %|| [ 1 ... 7 ]@1x2142
% %|| .a
% %|| .fdim
% %|| .labels
% %|| { 'chan'
% %|| 'time' }
% %|| .values
% %|| { { <char>@1x7 ... <char>@1x7 }@1x306
% %|| [ -0.2 ... 0.1 ]@1x7 }
% %|| .meeg
% %|| .samples_type
% %|| 'timelock'
% %|| .samples_field
% %|| 'trial'
% %|| .samples_label
% %|| 'rpt'
%
% % (This example requires FieldTrip)
% cosmo_skip_test_if_no_external('fieldtrip');
% %
% % As above, but combine the two types of channels
% % Here the axial channels only have axial neighbors, and the planar
% % channels only have planar neighbors. With 7 timepoints and 10
% % neighboring channels, the meg_axial channels all have 70 axial
% % neighbors while the meg_planar_combined channels all have
% % 140 neighbors based on the planar channel pairs in the original
% % dataset
% ds=cosmo_synthetic_dataset('type','meeg','size','big');
% nbrhood=cosmo_meeg_chan_neighborhood(ds,...
% 'chantype','all_combined','count',10);
% cosmo_disp(nbrhood,'edgeitems',1);
% %|| .neighbors
% %|| { [ 1 ... 2.07e+03 ]@1x70
% %|| :
% %|| [ 71 ... 2.14e+03 ]@1x140 }@204x1
% %|| .fa
% %|| .chan
% %|| [ 1 ... 204 ]@1x204
% %|| .a
% %|| .fdim
% %|| .labels
% %|| { 'chan' }
% %|| .values
% %|| { { 'MEG0111' ... 'MEG2642+2643' }@1x204 }
% %|| .meeg
% %|| .samples_type
% %|| 'timelock'
% %|| .samples_field
% %|| 'trial'
% %|| .samples_label
% %|| 'rpt'
% %|| .origin
% %|| .fa
% %|| .chan
% %|| [ 1 ... 306 ]@1x2142
% %|| .time
% %|| [ 1 ... 7 ]@1x2142
% %|| .a
% %|| .fdim
% %|| .labels
% %|| { 'chan'
% %|| 'time' }
% %|| .values
% %|| { { <char>@1x7 ... <char>@1x7 }@1x306
% %|| [ -0.2 ... 0.1 ]@1x7 }
% %|| .meeg
% %|| .samples_type
% %|| 'timelock'
% %|| .samples_field
% %|| 'trial'
% %|| .samples_label
% %|| 'rpt'
%
%
% Notes:
%
% See also: cosmo_meeg_chan_neighbors
%
% # For CoSMoMVPA's copyright information and license terms, #
% # see the COPYING file distributed with CoSMoMVPA. #
[dim,unused,attr_name,dim_name,ds_label]=cosmo_dim_find(ds,...
'chan',true);
nbrs=get_neighbors(ds,varargin{:});
ds_chan=ds.(attr_name).chan;
[nbrs_label,chan_idxs]=get_neighbor_indices(nbrs,ds_label,ds_chan);
nbrhood=struct();
nbrhood.neighbors=chan_idxs;
nbrhood.(attr_name).chan=1:numel(nbrs);
nbrhood.a=ds.a;
nbrhood.a.(dim_name)=struct();
nbrhood.a.(dim_name).labels={'chan'};
nbrhood.a.(dim_name).values={nbrs_label(:)'};
if dim==2
other_dim_name='sdim';
else
other_dim_name='fdim';
end
if isfield(nbrhood.a,other_dim_name)
nbrhood.a=rmfield(nbrhood.a,other_dim_name);
end
origin=struct();
origin.(attr_name)=ds.(attr_name);
origin.a=ds.a;
nbrhood.origin=origin;
function [nbrs_label,chan_idxs]=get_neighbor_indices(nbrs,ds_label,ds_chan)
nbrs_label={nbrs.label};
ds_label_cell=cellfun(@(x){x},ds_label,'UniformOutput',false);
ncenter=numel(nbrs_label);
ow=cosmo_overlap({nbrs.neighblabel},ds_label_cell);
[chan_pos,chan_cell]=cosmo_index_unique({ds_chan});
chan=chan_cell{1};
%
chan_idxs=cell(ncenter,1);
for k=1:ncenter
% find the indices in ds where the neighbors match
idx=find(ow(k,:));
chan_pos_msk=cosmo_match(chan,idx);
chan_idx=cat(1,chan_pos{chan_pos_msk});
if isempty(chan_idx)
chan_idx=zeros(0,1);
end
chan_idxs{k}=chan_idx';
end
function nbrs=get_neighbors(ds, varargin)
if numel(varargin)==1 && is_neighborhood_struct(varargin{1})
nbrs=varargin{1};
else
nbrs=cosmo_meeg_chan_neighbors(ds,varargin{:});
end
verify_neighbors(nbrs);
function tf=is_neighborhood_struct(nbrs)
tf=isstruct(nbrs) && ...
isfield(nbrs,'label') && ...
isfield(nbrs,'neighblabel');
function verify_neighbors(nbrs)
if ~is_neighborhood_struct(nbrs);
error('neighbor struct must be neighborhood struct');
end