--- a +++ b/functions/adminfunc/eeg_checkchanlocs.m @@ -0,0 +1,365 @@ +% EEG_CHECKCHANLOCS - Check the consistency of the channel locations structure +% of an EEGLAB dataset. +% +% Usage: +% >> EEG = eeg_checkchanlocs(EEG); +% >> [chanlocs chaninfo] = eeg_checkchanlocs( chanlocs, chaninfo); +% +% Inputs: +% EEG - EEG dataset +% chanlocs - EEG.chanlocs structure +% chaninfo - EEG.chaninfo structure +% +% Outputs: +% EEG - new EEGLAB dataset with updated channel location structures +% EEG.chanlocs, EEG.urchanlocs, EEG.chaninfo +% chanlocs - updated channel location structure +% chaninfo - updated chaninfo structure +% +% Author: Arnaud Delorme, SCCN/INC/UCSD, March 2, 2011 + +% Copyright (C) SCCN/INC/UCSD, March 2, 2011, arno@salk.edu +% +% This file is part of EEGLAB, see http://www.eeglab.org +% for the documentation and details. +% +% Redistribution and use in source and binary forms, with or without +% modification, are permitted provided that the following conditions are met: +% +% 1. Redistributions of source code must retain the above copyright notice, +% this list of conditions and the following disclaimer. +% +% 2. Redistributions in binary form must reproduce the above copyright notice, +% this list of conditions and the following disclaimer in the documentation +% and/or other materials provided with the distribution. +% +% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +% ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +% SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +% INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +% CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +% ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +% THE POSSIBILITY OF SUCH DAMAGE. + +% Hey Arno -- this is a quick fix to make an analysis work for Makoto +% I think the old version had a bug... + +function [chans, chaninfo, chanedit]= eeg_checkchanlocs(chans, chaninfo) + +if nargin < 1 + help eeg_checkchanlocs; + return; +end + +if nargin < 2 + chaninfo = []; +end + +processingEEGstruct = 0; +if isfield(chans, 'data') + processingEEGstruct = 1; + tmpEEG = chans; + [chans, chaninfo] = insertchans(tmpEEG.chanlocs, tmpEEG.chaninfo); + + % force Nosedir to +X (done here because of DIPFIT) + % ------------------- + if isfield(tmpEEG.chaninfo, 'nosedir') + if ~strcmpi(tmpEEG.chaninfo.nosedir, '+x') && all(isfield(tmpEEG.chanlocs,{'X','Y','theta','sph_theta'})) + disp(['Note for expert users: Nose direction is now set from ''' upper(tmpEEG.chaninfo.nosedir) ''' to default +X in EEG.chanlocs']); + [~, chaninfo, chans] = eeg_checkchanlocs(tmpEEG.chanlocs, tmpEEG.chaninfo); % Merge all channels for rotation (FID and data channels) + if strcmpi(chaninfo.nosedir, '+y') + rotate = 270; + elseif strcmpi(chaninfo.nosedir, '-x') + rotate = 180; + else + rotate = 90; + end + for index = 1:length(chans) + rotategrad = rotate/180*pi; + coord = (chans(index).Y + chans(index).X*sqrt(-1))*exp(sqrt(-1)*-rotategrad); + chans(index).Y = real(coord); + chans(index).X = imag(coord); + + if ~isempty(chans(index).theta) + chans(index).theta = chans(index).theta -rotate; + chans(index).sph_theta = chans(index).sph_theta+rotate; + if chans(index).theta <-180, chans(index).theta =chans(index).theta +360; end + if chans(index).sph_theta>180 , chans(index).sph_theta=chans(index).sph_theta-360; end + end + end + + if isfield(tmpEEG, 'dipfit') + if isfield(tmpEEG.dipfit, 'coord_transform') + if isempty(tmpEEG.dipfit.coord_transform) + tmpEEG.dipfit.coord_transform = [0 0 0 0 0 0 1 1 1]; + end + tmpEEG.dipfit.coord_transform(6) = tmpEEG.dipfit.coord_transform(6)+rotategrad; + end + end + + chaninfo.originalnosedir = chaninfo.nosedir; + chaninfo.nosedir = '+X'; + end + end + chanedit = chans; + complicated = true; +else + if ~isfield(chans, 'datachan') + [chanedit,dummy,complicated] = insertchans(chans, chaninfo); + else + chanedit = chans; + complicated = true; + end +end + +nosevals = { '+X' '-X' '+Y' '-Y' }; +if ~isfield(chaninfo, 'plotrad'), chaninfo.plotrad = []; end +if ~isfield(chaninfo, 'shrink'), chaninfo.shrink = []; end +if ~isfield(chaninfo, 'nosedir'), chaninfo.nosedir = nosevals{1}; end + +% handles deprecated fields +% ------------------------- +plotrad = []; +if isfield(chanedit, 'plotrad'), + plotrad = chanedit(1).plotrad; + chanedit = rmfield(chanedit, 'plotrad'); + if ischar(plotrad) && ~isempty(str2num(plotrad)), plotrad = str2num(plotrad); end + chaninfo.plotrad = plotrad; +end +if isfield(chanedit, 'shrink') && ~isempty(chanedit(1).shrink) + shrinkorskirt = 1; + if ~ischar(chanedit(1).shrink) + plotrad = 0.5/(1-chanedit(1).shrink); % convert old values + end + chanedit = rmfield(chanedit, 'shrink'); + chaninfo.plotrad = plotrad; +end + +% set non-existent fields to [] +% ----------------------------- +fields = { 'labels' 'theta' 'radius' 'X' 'Y' 'Z' 'sph_theta' 'sph_phi' 'sph_radius' 'type' 'ref' 'urchan' }; +fieldtype = { 'str' 'num' 'num' 'num' 'num' 'num' 'num' 'num' 'num' 'str' 'str' 'num' }; +check_newfields = true; %length(fieldnames(chanedit)) < length(fields); +if ~isempty(chanedit) + for index = 1:length(fields) + if check_newfields && ~isfield(chanedit, fields{index}) + % new field + % --------- + if strcmpi(fieldtype{index}, 'num') + chanedit = setfield(chanedit, {1}, fields{index}, []); + else + for indchan = 1:length(chanedit) + chanedit = setfield(chanedit, {indchan}, fields{index}, ''); + end + end + else + % existing fields + % --------------- + allvals = {chanedit.(fields{index})}; + if isnumeric(allvals{1}) && any(cellfun(@(x)~isempty(x) & all(isnan(x)), allvals)) + posNaNs = find(cellfun(@(x)~isempty(x) & all(isnan(x)), allvals)); + for iPos = 1:length(posNaNs) + chanedit = setfield(chanedit, {posNaNs(iPos)}, fields{index}, []); + end + end + if strcmpi(fieldtype{index}, 'num') + if ~all(cellfun('isclass',allvals,'double')) + nomconvert = cellfun(@isinteger, allvals); + if any(nomconvert) + for indConvert = find(nomconvert) + chanedit = setfield(chanedit, {indConvert}, fields{index}, double(allvals{indConvert})); + end + end + allvals = {chanedit.(fields{index})}; + numok = cellfun(@isfloat, allvals); + if any(numok == 0) + for indConvert = find(numok == 0) + chanedit = setfield(chanedit, {indConvert}, fields{index}, []); + end + end + end + else + strok = cellfun('isclass', allvals,'char'); + if strcmpi(fields{index}, 'labels'), prefix = 'E'; else prefix = ''; end + if any(strok == 0) + for indConvert = find(strok == 0) + try + strval = [ prefix num2str(getfield(chanedit, {indConvert}, fields{index})) ]; + chanedit = setfield(chanedit, {indConvert}, fields{index}, strval); + catch + chanedit = setfield(chanedit, {indConvert}, fields{index}, ''); + end + end + end + end + end + end +end + +if ~isequal(fieldnames(chanedit)',fields) + try + chanedit = orderfields(chanedit, fields); + catch, end +end + + +% check channel labels +if isfield(chanedit, 'labels') + % prefix (EDF format specification)? + if strfind([chanedit.labels], 'EEG') % `contains() is not back compatible + chanprefixes = {'EEG-', 'EEG ', 'EEG' }; % order matters + tmp = {chanedit.labels}; + if sum(~isnan(str2double( strrep(tmp, 'EEG', '')))) < 30 % more than 30 numerical channels, i.e., EEG001, do nothing + disp('Detected/removing ''EEG'' prefix from channel labels') + for idx = 1:length(chanprefixes) + tmp = strrep(tmp, chanprefixes(idx), ''); + end + [chanedit.labels] = deal(tmp{:}); + end + end + if strfind([chanedit.labels], 'RDA_') % `contains() is not back compatible + chanprefixes = { 'BrainVision RDA_' 'RDA_' }; % order matters + tmp = {chanedit.labels}; + disp('Detected/removing prefix from channel labels') + for idx = 1:length(chanprefixes) + tmp = strrep(tmp, chanprefixes(idx), ''); + end + [chanedit.labels] = deal(tmp{:}); + end + + % remove simple quotes or double quotes from channel labels + if sum(chanedit(1).labels == '''') == 2 + tmp = {chanedit.labels}; + tmp = strrep(tmp, '''', ''); + [chanedit.labels] = deal(tmp{:}); + end + if sum(chanedit(1).labels == '"') == 2 + tmp = {chanedit.labels}; + tmp = strrep(tmp, '"', ''); + [chanedit.labels] = deal(tmp{:}); + end + + % duplicate labels? + tmp = sort({chanedit.labels}); + if any(strcmp(tmp(1:end-1),tmp(2:end))) + disp('Warning: some channels have the same label'); + end + + % empty labels? + indEmpty = find(cellfun(@isempty, {chanedit.labels})); + if ~isempty(indEmpty) + tmpWarning = warning('backtrace'); + warning backtrace off; + warning('channel labels should not be empty, creating unique labels'); + warning(tmpWarning); + for index = indEmpty + chanedit(index).labels = sprintf('E%d', index); + end + end + + % handle MEG + if ~isfield(chaninfo, 'topoplot') + if ~isempty(strfind([chanedit(1).labels], 'MLC11')) || (isfield(chanedit, 'type') && ~isempty(strfind(chanedit(1).type, 'meg'))) + disp('MEG data detected and topoplot options not set, so setting them in EEG.chaninfo') + chaninfo.topoplot = { 'conv' 'on' 'headrad' 0.3 }; + end + end +end + +% remove fields +% ------------- +if isfield(chanedit, 'sph_phi_besa' ), chanedit = rmfield(chanedit, 'sph_phi_besa'); end +if isfield(chanedit, 'sph_theta_besa'), chanedit = rmfield(chanedit, 'sph_theta_besa'); end + +% Check if some channels need conversion +% -------------------------------------- +chanX = cellfun('isempty',{ chanedit.X }); +chanTheta = cellfun('isempty',{ chanedit.theta }); +chanSphTheta = cellfun('isempty',{ chanedit.sph_theta }); +if any(~chanX & chanTheta) || any(~chanSphTheta & chanTheta) || any(~chanX & chanSphTheta) + try + % convert them all + chanedit = convertlocs(chanedit,'auto'); + catch + disp('eeg_checkchanlocs: Unable to convert electrode locations between coordinate systems'); + end +end + +% reconstruct the chans structure +% ------------------------------- +if complicated + [chans, chaninfo.nodatchans] = getnodatchan( chanedit ); + if ~isfield(chaninfo, 'nodatchans'), chaninfo.nodatchans = []; end + if isempty(chanedit) + for iField = 1:length(fields) + chanedit = setfield(chanedit, fields{iField}, []); + end + end +else + chans = rmfield(chanedit,'datachan'); + chaninfo.nodatchans = []; +end + +if processingEEGstruct + tmpEEG.chanlocs = chans; + tmpEEG.chaninfo = chaninfo; + chans = tmpEEG; +end + +% --------------------------------------------- +% separate data channels from non-data channels +% --------------------------------------------- +function [chans, fidsval] = getnodatchan(chans) +if isfield(chans,'datachan') + [chans(cellfun('isempty',{chans.datachan})).datachan] = deal(0); + fids = [chans.datachan] == 0; + fidsval = chans(fids); + chans = rmfield(chans(~fids),'datachan'); +else + fids = []; +end + +% ---------------------------------------- +% fuse data channels and non-data channels +% ---------------------------------------- +function [chans, chaninfo,complicated] = insertchans(chans, chaninfo, nchans) +if nargin < 3, nchans = length(chans); end +[chans.datachan] = deal(1); +complicated = false; % whether we need complicated treatment of datachans & co further down the road..... + +if isfield(chans,'type') + mask = strcmpi({chans.type},'FID') | strcmpi({chans.type},'IGNORE'); + if any(mask) + [chans(mask).datachan] = deal(0); + complicated = true; + end +end +if length(chans) > nchans && nchans ~= 0 % reference at the end of the structure + chans(end).datachan = 0; + complicated = true; +end +if isfield(chaninfo, 'nodatchans') + if ~isempty(chaninfo.nodatchans) && isstruct(chaninfo.nodatchans) + chanlen = length(chans); + for index = 1:length(chaninfo.nodatchans) + fields = fieldnames( chaninfo.nodatchans ); + ind = chanlen+index; + for f = 1:length( fields ) + chans = setfield(chans, { ind }, fields{f}, getfield( chaninfo.nodatchans, { index }, fields{f})); + end + chans(ind).datachan = 0; + complicated = true; + end + chaninfo = rmfield(chaninfo, 'nodatchans'); + + % put these channels first + % ------------------------ + % tmp = chans(chanlen+1:end); + % chans(length(tmp)+1:end) = chans(1:end-length(tmp)); + % chans(1:length(tmp)) = tmp; + end +end