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 . % % $Id: ft_prepare_neighbours.m 9521 2014-05-14 09:45:42Z roboos $ revision = '$Id: ft_prepare_neighbours.m 9521 2014-05-14 09:45:42Z roboos $'; % 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 neighbdist; neighbours(i).dist(idx) = []; neighbours(i).neighblabel(idx) = []; end neighbours = rmfield(neighbours, 'dist');