From d1d7ae8285fccbbfe2d607b73187d07e2cb26224 Mon Sep 17 00:00:00 2001 From: Sarang Dalal Date: Tue, 19 Jul 2016 14:08:04 +0200 Subject: [PATCH 01/11] The needed function is also provided by SPM12; toolbox check updated to check for SPM12 if SPM8 not found --- utilities/private/sn2individual.m | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/utilities/private/sn2individual.m b/utilities/private/sn2individual.m index 6827c165b0..9b8a3a869d 100644 --- a/utilities/private/sn2individual.m +++ b/utilities/private/sn2individual.m @@ -34,7 +34,10 @@ warped = ft_warp_apply(T, input); else % we need the spm_dctmtx function for the nonlinear case - ft_hastoolbox('spm8', 1); + hasSPM8 = ft_hastoolbox('spm8', 3); + if(~hasSPM8) + ft_hastoolbox('spm12',1); + end dim = P.VG.dim(1:3); xyz = ft_warp_apply(inv(P.VG.mat), input); % goes into voxel coordinates From 97373ffbe66a68207c636005ae0eee8d6f343240 Mon Sep 17 00:00:00 2001 From: Teresa Madsen Date: Fri, 9 Sep 2016 14:06:06 -0400 Subject: [PATCH 02/11] add support for intervals and other events in NEX files also added some workarounds for rare errors and related documentation --- fileio/private/read_nex_event.m | 126 ++++++++++++++++++++++++++------ 1 file changed, 104 insertions(+), 22 deletions(-) diff --git a/fileio/private/read_nex_event.m b/fileio/private/read_nex_event.m index 4af4f58b17..2594d6f702 100644 --- a/fileio/private/read_nex_event.m +++ b/fileio/private/read_nex_event.m @@ -1,15 +1,23 @@ function [event] = read_nex_event(filename) -% READ_NEX_EVENT for Plexon *.nex file +% READ_NEX_EVENT for Plexon *.nex file, supports NEX variable types: +% marker, interval, and event % % Use as % [event] = read_nex_event(filename) % +% The event.type used to select events in ft_trialfun_general is the +% variable name from the NEX file (hdr.varheader.name - not to be confused +% with hdr.varheader.type). +% % The sample numbers returned in event.sample correspond with the % timestamps, correcting for the difference in sampling frequency in the % continuous LFP channels and the system sampling frequency. Assuming 40kHz % sampling frequency for the system and 1kHz for the LFP channels, it is % event.sample = timestamp / (40000/1000); +% If there are no continuous variables in the file, the system sampling +% frequency is used throughout, so +% event.sample = timestamp; % % See also READ_NEX_HEADER, READ_NEX_DATA @@ -35,33 +43,107 @@ hdr = read_nex_header(filename); adindx = find(cell2mat({hdr.varheader.typ})==5); -smpfrq = hdr.varheader(adindx(1)).wfrequency; +if isempty(adindx) % this would otherwise produce an error + warning('No continuous variables found - using hdr.filheader.frequency'); + smpfrq = hdr.filheader.frequency; +else + smpfrq = hdr.varheader(adindx(1)).wfrequency; +end + +fid=fopen(filename,'r','ieee-le'); +event = struct('sample',{},'value',{},'timestamp',{},'type',{}, ... + 'duration',{},'offset',{}); -% find the channel with the strobed trigger +% find the channel with the strobed trigger ("marker") mrkvarnum = find([hdr.varheader.typ] == 6); -fid=fopen(filename,'r','ieee-le'); -status = fseek(fid,hdr.varheader(mrkvarnum).offset,'bof'); +if ~isempty(mrkvarnum) % both of these cases would have produced errors + if numel(mrkvarnum) > 1 + warning('file contains >1 "marker" variable, reading only the first'); + mrkvarnum = mrkvarnum(1); + end + status = fseek(fid,hdr.varheader(mrkvarnum).offset,'bof'); + if status < 0; error('error with fseek'); end + + % read the time of the triggers + dum = fread(fid,hdr.varheader(mrkvarnum).cnt,'int32'); + timestamp = dum; + dum = dum ./(hdr.filheader.frequency./smpfrq); + mrk.tim = round(dum); + + % read the value of the triggers + status = fseek(fid,64,'cof'); + if status < 0; error('error with fseek'); end + dum = fread(fid, [hdr.varheader(mrkvarnum).mrklen, ... + hdr.varheader(mrkvarnum).cnt], 'uchar'); + mrk.val = str2num(char(dum(1:5,:)')); %#ok non-scalar + + % translate into an FCDC event structure + Nevent = length(mrk.tim); + for i=1:Nevent + event(i).sample = mrk.tim(i); + event(i).value = mrk.val(i); + event(i).timestamp = timestamp(i); + event(i).type = hdr.varheader(mrkvarnum).nam; + event(i).duration = 1; + event(i).offset = 0; + end +end -% read the time of the triggers -dum = fread(fid,hdr.varheader(mrkvarnum).cnt,'int32'); -timestamp = dum; -dum = dum ./(hdr.filheader.frequency./smpfrq); -mrk.tim = round(dum); +% find interval channels +intvarnum = find([hdr.varheader.typ] == 2); -% read the value of the triggers -status = fseek(fid,64,'cof'); -dum = fread(fid,[hdr.varheader(mrkvarnum).mrklen,hdr.varheader(mrkvarnum).cnt],'uchar'); -mrk.val = str2num(char(dum(1:5,:)')); +for int = 1:numel(intvarnum) + status = fseek(fid,hdr.varheader(intvarnum(int)).offset,'bof'); + if status < 0; error('error with fseek'); end -status = fclose(fid); + % read the time of the triggers + dum1 = fread(fid,hdr.varheader(intvarnum(int)).cnt,'int32'); + dum2 = fread(fid,hdr.varheader(intvarnum(int)).cnt,'int32'); + timestamp = dum1; + dum1 = dum1 ./(hdr.filheader.frequency./smpfrq); + dum2 = dum2 ./(hdr.filheader.frequency./smpfrq); + evt.tim = round(dum1); + evt.dur = round(dum2-dum1); + + % translate into an FCDC event structure + Nold = length(event); + Nevent = length(evt.tim); + for i=1:Nevent + event(Nold+i).sample = evt.tim(i); + event(Nold+i).value = []; + event(Nold+i).timestamp = timestamp(i); + event(Nold+i).type = hdr.varheader(intvarnum(int)).nam; + event(Nold+i).duration = evt.dur(i); + event(Nold+i).offset = 0; + end +end + +% find event channels +evtvarnum = find([hdr.varheader.typ] == 1); -% translate into an FCDC event structure -Nevent = length(mrk.tim); -event = struct('sample', num2cell(mrk.tim), 'value', num2cell(mrk.val), 'timestamp', num2cell(timestamp)); -for i=1:Nevent - event(i).type = hdr.varheader(mrkvarnum).nam; - event(i).duration = 1; - event(i).offset = 0; +for ev = 1:numel(evtvarnum) + status = fseek(fid,hdr.varheader(evtvarnum(ev)).offset,'bof'); + if status < 0; error('error with fseek'); end + + % read the time of the triggers + dum = fread(fid,hdr.varheader(evtvarnum(ev)).cnt,'int32'); + timestamp = dum; + dum = dum ./(hdr.filheader.frequency./smpfrq); + evt.tim = round(dum); + + % translate into an FCDC event structure + Nold = length(event); + Nevent = length(evt.tim); + for i=1:Nevent + event(Nold+i).sample = evt.tim(i); + event(Nold+i).value = []; + event(Nold+i).timestamp = timestamp(i); + event(Nold+i).type = hdr.varheader(evtvarnum(ev)).nam; + event(Nold+i).duration = 1; + event(Nold+i).offset = 0; + end end +status = fclose(fid); +if status < 0; error('error with fclose'); end From 34fdd9b831ed8389c2129a3354049aa1c9691f34 Mon Sep 17 00:00:00 2001 From: Teresa Madsen Date: Tue, 13 Sep 2016 14:03:46 -0400 Subject: [PATCH 03/11] fix problems with read_nex_events.m discovered through test_bug2093.m --- fileio/private/read_nex_event.m | 91 +++++++++++++++++++-------------- test/test_bug2093.m | 84 ++++++++++++++++++++++++++++++ 2 files changed, 136 insertions(+), 39 deletions(-) create mode 100644 test/test_bug2093.m diff --git a/fileio/private/read_nex_event.m b/fileio/private/read_nex_event.m index 2594d6f702..d01b15d924 100644 --- a/fileio/private/read_nex_event.m +++ b/fileio/private/read_nex_event.m @@ -54,19 +54,15 @@ event = struct('sample',{},'value',{},'timestamp',{},'type',{}, ... 'duration',{},'offset',{}); -% find the channel with the strobed trigger ("marker") +% find any channels with strobed triggers ("markers") mrkvarnum = find([hdr.varheader.typ] == 6); -if ~isempty(mrkvarnum) % both of these cases would have produced errors - if numel(mrkvarnum) > 1 - warning('file contains >1 "marker" variable, reading only the first'); - mrkvarnum = mrkvarnum(1); - end - status = fseek(fid,hdr.varheader(mrkvarnum).offset,'bof'); +for mrkn = 1:numel(mrkvarnum) + status = fseek(fid,hdr.varheader(mrkvarnum(mrkn)).offset,'bof'); if status < 0; error('error with fseek'); end % read the time of the triggers - dum = fread(fid,hdr.varheader(mrkvarnum).cnt,'int32'); + dum = fread(fid,hdr.varheader(mrkvarnum(mrkn)).cnt,'int32'); timestamp = dum; dum = dum ./(hdr.filheader.frequency./smpfrq); mrk.tim = round(dum); @@ -74,20 +70,26 @@ % read the value of the triggers status = fseek(fid,64,'cof'); if status < 0; error('error with fseek'); end - dum = fread(fid, [hdr.varheader(mrkvarnum).mrklen, ... - hdr.varheader(mrkvarnum).cnt], 'uchar'); + dum = fread(fid, [hdr.varheader(mrkvarnum(mrkn)).mrklen, ... + hdr.varheader(mrkvarnum(mrkn)).cnt], 'uchar'); mrk.val = str2num(char(dum(1:5,:)')); %#ok non-scalar % translate into an FCDC event structure Nevent = length(mrk.tim); - for i=1:Nevent - event(i).sample = mrk.tim(i); - event(i).value = mrk.val(i); - event(i).timestamp = timestamp(i); - event(i).type = hdr.varheader(mrkvarnum).nam; - event(i).duration = 1; - event(i).offset = 0; - end + tmp = struct('sample',num2cell(mrk.tim), 'value',num2cell(mrk.val), ... + 'timestamp',num2cell(timestamp), ... + 'type',repmat({hdr.varheader(mrkvarnum(mrkn)).nam},[Nevent,1]), ... + 'duration',num2cell(ones(Nevent,1)), ... + 'offset',num2cell(zeros(Nevent,1))); + event = [event; tmp]; %#ok<*AGROW> way faster than adding them 1-by-1 +% for i=1:Nevent +% event(Nold+i,1).sample = mrk.tim(i); +% event(Nold+i,1).value = mrk.val(i); +% event(Nold+i,1).timestamp = timestamp(i); +% event(Nold+i,1).type = hdr.varheader(mrkvarnum(mrkn)).nam; +% event(Nold+i,1).duration = 1; +% event(Nold+i,1).offset = 0; +% end end % find interval channels @@ -103,20 +105,25 @@ timestamp = dum1; dum1 = dum1 ./(hdr.filheader.frequency./smpfrq); dum2 = dum2 ./(hdr.filheader.frequency./smpfrq); - evt.tim = round(dum1); - evt.dur = round(dum2-dum1); + intevt.tim = round(dum1); + intevt.dur = round(dum2-dum1); % translate into an FCDC event structure - Nold = length(event); - Nevent = length(evt.tim); - for i=1:Nevent - event(Nold+i).sample = evt.tim(i); - event(Nold+i).value = []; - event(Nold+i).timestamp = timestamp(i); - event(Nold+i).type = hdr.varheader(intvarnum(int)).nam; - event(Nold+i).duration = evt.dur(i); - event(Nold+i).offset = 0; - end +% Nold = length(event); + Nevent = length(intevt.tim); + tmp = struct('sample',num2cell(intevt.tim), 'value',cell(Nevent,1), ... + 'timestamp',num2cell(timestamp), ... + 'type',repmat({hdr.varheader(intvarnum(int)).nam},[Nevent,1]), ... + 'duration',num2cell(intevt.dur), 'offset',num2cell(zeros(Nevent,1))); + event = [event; tmp]; +% for i=1:Nevent +% event(Nold+i,1).sample = intevt.tim(i,1); +% event(Nold+i,1).value = []; +% event(Nold+i,1).timestamp = timestamp(i,1); +% event(Nold+i,1).type = hdr.varheader(intvarnum(int)).nam; +% event(Nold+i,1).duration = intevt.dur(i,1); +% event(Nold+i,1).offset = 0; +% end end % find event channels @@ -133,16 +140,22 @@ evt.tim = round(dum); % translate into an FCDC event structure - Nold = length(event); + % Nold = length(event); Nevent = length(evt.tim); - for i=1:Nevent - event(Nold+i).sample = evt.tim(i); - event(Nold+i).value = []; - event(Nold+i).timestamp = timestamp(i); - event(Nold+i).type = hdr.varheader(evtvarnum(ev)).nam; - event(Nold+i).duration = 1; - event(Nold+i).offset = 0; - end + tmp = struct('sample',num2cell(evt.tim), 'value',cell(Nevent,1), ... + 'timestamp',num2cell(timestamp), ... + 'type',repmat({hdr.varheader(evtvarnum(ev)).nam},[Nevent,1]), ... + 'duration',num2cell(ones(Nevent,1)), ... + 'offset',num2cell(zeros(Nevent,1))); + event = [event; tmp]; +% for i=1:Nevent +% event(Nold+i,1).sample = evt.tim(i,1); +% event(Nold+i,1).value = []; +% event(Nold+i,1).timestamp = timestamp(i,1); +% event(Nold+i,1).type = hdr.varheader(evtvarnum(ev)).nam; +% event(Nold+i,1).duration = 1; +% event(Nold+i,1).offset = 0; +% end end status = fclose(fid); diff --git a/test/test_bug2093.m b/test/test_bug2093.m new file mode 100644 index 0000000000..931f3ce451 --- /dev/null +++ b/test/test_bug2093.m @@ -0,0 +1,84 @@ +function test_bug2093 +%TEST_BUG2093 Compares output of same test NEX file from ft_read_header, +% ft_read_data, and ft_read_event using old (master branch) and new +% (nexhandling branch) versions of the FieldTrip code + +testnexfile = 'S:\Teresa\Analyses\FieldTrip\test files\p213parall.nex'; + +if ~exist(['data_bug2093' filesep 'bug2093.mat'], 'file') + % run only once, using old fieldtrip code + git checkout master + + old.hdr = ft_read_header(testnexfile); + old.dat = ft_read_data(testnexfile); + old.evt = ft_read_event(testnexfile); + + git checkout nexhandling + save(['data_bug2093' filesep 'bug2093.mat'], 'old') +else + % all future test executions with newer FT code + load(['data_bug2093' filesep 'bug2093.mat']) +end + +% new.hdr = ft_read_header(testnexfile); +% new.dat = ft_read_data(testnexfile); +new.evt = ft_read_event(testnexfile); + +% make sure the old header and data are identical, since I haven't changed +% them yet +% assert(isequal(old.hdr,new.hdr)) +% assert(isequaln(old.dat,new.dat)) % data padded with NaNs should still be = + +%% make sure all old events are still present, despite adding more +assert(isequal(fieldnames(old.evt), fieldnames(new.evt))) +fnames = fieldnames(old.evt); +oldcell = cell(1,numel(fnames)); +newcell = cell(1,numel(fnames)); +mtnew = false(numel(new.evt),numel(fnames)); +for fieldn = 1:numel(fnames) + if ischar(old.evt(1).(fnames{fieldn})) && ... + ischar(new.evt(1).(fnames{fieldn})) + oldcell{fieldn} = {old.evt.(fnames{fieldn})}'; + newcell{fieldn} = {new.evt.(fnames{fieldn})}'; + else + oldcell{fieldn} = vertcat(old.evt.(fnames{fieldn})); + newcell{fieldn} = vertcat(new.evt.(fnames{fieldn})); + end + assert(isequal(class(oldcell{fieldn}), class(newcell{fieldn}))) + + if ~isequal(size(oldcell{fieldn},1), numel(old.evt)) + error('This strategy won''t work.') + end + if ~isequal(size(newcell{fieldn},1), numel(new.evt)) + for evn = 1:numel(new.evt) + mtnew(evn,fieldn) = isempty(new.evt(evn).(fnames{fieldn})); + end + if ~isequal(size(newcell{fieldn},1), sum(~mtnew(:,fieldn))) + error('Empty fields don''t account for difference in event #s.') + end + end +end + +oldevttable = struct2table(old.evt); +% Empty values in numeric variables cause the column to be represented as a +% cell array when the structure is converted to a table, which is not +% supported by ismember. The empty values should only be in the new event +% types I added support for, so they can be safely excluded from the table +% when verifying that all old event rows are contained in the new event +% data. +newrows = ~any(mtnew,2); +newevttable = table; +for fieldn = 1:numel(fnames) + if isequal(size(newcell{fieldn},1), numel(newrows)) + newevttable = [newevttable table(newcell{fieldn}(newrows,:), ... + 'VariableNames',fnames(fieldn))]; + elseif isequal(size(newcell{fieldn},1), sum(newrows)) + newevttable = [newevttable table(newcell{fieldn}, ... + 'VariableNames',fnames(fieldn))]; + else + error('There may be different empty cells in different event fields.') + end +end + +assert(all(ismember(oldevttable,newevttable))) +disp('All old events found in new event output.') \ No newline at end of file From d2d7f75d632bff5c13b19c18241a1bd614f49f45 Mon Sep 17 00:00:00 2001 From: Teresa Madsen Date: Tue, 13 Sep 2016 14:10:38 -0400 Subject: [PATCH 04/11] change location of test data files --- test/test_bug2093.m | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/test_bug2093.m b/test/test_bug2093.m index 931f3ce451..6bffef5894 100644 --- a/test/test_bug2093.m +++ b/test/test_bug2093.m @@ -4,8 +4,9 @@ % (nexhandling branch) versions of the FieldTrip code testnexfile = 'S:\Teresa\Analyses\FieldTrip\test files\p213parall.nex'; +testmatfile = 'S:\Teresa\Analyses\FieldTrip\test files\bug2093.mat'; -if ~exist(['data_bug2093' filesep 'bug2093.mat'], 'file') +if ~exist(testmatfile, 'file') % run only once, using old fieldtrip code git checkout master @@ -14,10 +15,10 @@ old.evt = ft_read_event(testnexfile); git checkout nexhandling - save(['data_bug2093' filesep 'bug2093.mat'], 'old') + save(testmatfile, 'old') else % all future test executions with newer FT code - load(['data_bug2093' filesep 'bug2093.mat']) + load(testmatfile) end % new.hdr = ft_read_header(testnexfile); From febb1cfbc3d205f634d04f5a54454b3d1bc14ab0 Mon Sep 17 00:00:00 2001 From: Teresa Madsen Date: Tue, 13 Sep 2016 14:15:08 -0400 Subject: [PATCH 05/11] improve comments in test_bug2093.m --- test/test_bug2093.m | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/test_bug2093.m b/test/test_bug2093.m index 6bffef5894..11a4bf3ff8 100644 --- a/test/test_bug2093.m +++ b/test/test_bug2093.m @@ -6,6 +6,7 @@ testnexfile = 'S:\Teresa\Analyses\FieldTrip\test files\p213parall.nex'; testmatfile = 'S:\Teresa\Analyses\FieldTrip\test files\bug2093.mat'; +%% first get output of old code if ~exist(testmatfile, 'file') % run only once, using old fieldtrip code git checkout master @@ -21,10 +22,12 @@ load(testmatfile) end +%% then get output of new code % new.hdr = ft_read_header(testnexfile); % new.dat = ft_read_data(testnexfile); new.evt = ft_read_event(testnexfile); +%% then compare the 2 % make sure the old header and data are identical, since I haven't changed % them yet % assert(isequal(old.hdr,new.hdr)) @@ -68,7 +71,7 @@ % when verifying that all old event rows are contained in the new event % data. newrows = ~any(mtnew,2); -newevttable = table; +newevttable = table; %#ok<*AGROW> for fieldn = 1:numel(fnames) if isequal(size(newcell{fieldn},1), numel(newrows)) newevttable = [newevttable table(newcell{fieldn}(newrows,:), ... From baeece9afeaa5fcc6a67017f97e49ed77f965b91 Mon Sep 17 00:00:00 2001 From: Sarang Dalal Date: Thu, 15 Sep 2016 19:27:29 +0200 Subject: [PATCH 06/11] nutmegtrip: added multiple panel support and various bug fixes --- contrib/nutmegtrip/nmt_animate.m | 2 +- contrib/nutmegtrip/nmt_sourceplot.m | 1214 +++++++++-------- contrib/nutmegtrip/nmt_spm_plot.m | 183 +-- contrib/nutmegtrip/nmt_spmfig_setup.m | 20 +- contrib/nutmegtrip/nmt_timeselect.m | 9 +- contrib/nutmegtrip/private/nmt_tfplot.m | 15 +- contrib/nutmegtrip/private/nmt_update_panel.m | 158 +++ 7 files changed, 818 insertions(+), 783 deletions(-) create mode 100644 contrib/nutmegtrip/private/nmt_update_panel.m diff --git a/contrib/nutmegtrip/nmt_animate.m b/contrib/nutmegtrip/nmt_animate.m index 4d2374c5d9..f4327698ad 100644 --- a/contrib/nutmegtrip/nmt_animate.m +++ b/contrib/nutmegtrip/nmt_animate.m @@ -15,7 +15,7 @@ function nmt_animate(cfg) Fs = 1/(st.nmt.time(2)-st.nmt.time(1)); - cfg.stepsize = ft_getopt(cfg, 'stepsize', round(10e-3 * Fs)); % default is 10ms step + cfg.stepsize = ft_getopt(cfg, 'stepsize', round(5e-3 * Fs)); % default is 5ms step cfg.winsize = ft_getopt(cfg,'winsize',1); cfg.winsize = cfg.winsize - 1; % simplifies index tracking diff --git a/contrib/nutmegtrip/nmt_sourceplot.m b/contrib/nutmegtrip/nmt_sourceplot.m index 6c2c96ba72..faeae1e025 100644 --- a/contrib/nutmegtrip/nmt_sourceplot.m +++ b/contrib/nutmegtrip/nmt_sourceplot.m @@ -50,7 +50,6 @@ function nmt_sourceplot(cfg,functional) % 'minzero', from min(funparameter) to 0 % 'auto', if funparameter values are all positive: 'zeromax', % all negative: 'minzero', both possitive and negative: 'maxabs' -% **TODO** cfg.colorbar = 'yes' or 'no' (default = 'yes') % % The following parameters can be used for the masking data: % **TODO** cfg.opacitymap = opacitymap for mask data, see ALPHAMAP (default = 'auto') @@ -179,645 +178,670 @@ function nmt_sourceplot(cfg,functional) % the abort variable is set to true or false in ft_preamble_init if ft_abort - return + return end % this is not supported any more as of 26/10/2011 if ischar(functional) - error('please use cfg.inputfile instead of specifying the input variable as a sting'); + error('please use cfg.inputfile instead of specifying the input variable as a string'); end -% ensure that old and unsupported options are not being relied on by the end-user's script -% instead of specifying cfg.coordsys, the user should specify the coordsys in the functional data -cfg = ft_checkconfig(cfg, 'forbidden', {'units', 'inputcoordsys', 'coordinates'}); -cfg = ft_checkconfig(cfg, 'deprecated', 'coordsys'); -cfg = ft_checkconfig(cfg, 'renamedval', {'funparameter', 'avg.pow', 'pow'}); -cfg = ft_checkconfig(cfg, 'renamedval', {'funparameter', 'avg.coh', 'coh'}); -cfg = ft_checkconfig(cfg, 'renamedval', {'funparameter', 'avg.mom', 'mom'}); -cfg = ft_checkconfig(cfg, 'renamedval', {'funparameter', 'avg.itc', 'itc'}); -cfg = ft_checkconfig(cfg, 'renamedval', {'funparameter', 'avg.tf', 'tf'}); -cfg = ft_checkconfig(cfg, 'renamedval', {'maskparameter', 'avg.pow', 'pow'}); -cfg = ft_checkconfig(cfg, 'renamedval', {'maskparameter', 'avg.coh', 'coh'}); -cfg = ft_checkconfig(cfg, 'renamedval', {'maskparameter', 'avg.mom', 'mom'}); -cfg = ft_checkconfig(cfg, 'renamedval', {'maskparameter', 'avg.itc', 'itc'}); -cfg = ft_checkconfig(cfg, 'renamedval', {'maskparameter', 'avg.tf', 'tf'}); - - -% set the defaults for all methods -cfg.funparameter = ft_getopt(cfg, 'funparameter', []); -cfg.maskparameter = ft_getopt(cfg, 'maskparameter', []); -cfg.title = ft_getopt(cfg, 'title', ''); -cfg.atlas = ft_getopt(cfg, 'atlas', []); -cfg.topoplot = ft_getopt(cfg, 'topoplot', ''); - -switch(getdimord(functional,cfg.funparameter)) - case '{pos}_freq_time' - cfg.plottype = ft_getopt(cfg, 'plottype', 'tf'); % default plot type is "time-freq" - otherwise - cfg.plottype = ft_getopt(cfg, 'plottype', 'ts'); % default plot type is "time series" -end - - -if isfield(cfg, 'atlas') && ~isempty(cfg.atlas) - % the atlas lookup requires the specification of the coordsys - functional = ft_checkdata(functional, 'datatype', {'volume', 'source'}, 'feedback', 'yes', 'hasunit', 'yes', 'hascoordsys', 'yes'); -else - % check if the input functional is valid for this function, a coordsys is not directly needed - functional = ft_checkdata(functional, 'datatype', {'volume', 'source'}, 'feedback', 'yes', 'hasunit', 'yes'); -end - -% determine the type of functional -issource = ft_datatype(functional, 'source'); -isvolume = ft_datatype(functional, 'volume'); - -% set the defaults for all methods -cfg.method = ft_getopt(cfg, 'method', 'ortho'); -cfg.funparameter = ft_getopt(cfg, 'funparameter', []); -cfg.maskparameter = ft_getopt(cfg, 'maskparameter', []); -cfg.downsample = ft_getopt(cfg, 'downsample', 1); -cfg.title = ft_getopt(cfg, 'title', ''); -cfg.atlas = ft_getopt(cfg, 'atlas', []); -cfg.marker = ft_getopt(cfg, 'marker', []); -cfg.markersize = ft_getopt(cfg, 'markersize', 5); -cfg.markercolor = ft_getopt(cfg, 'markercolor', [1 1 1]); - -if ~isfield(cfg, 'anaparameter') - if isfield(functional, 'anatomy') - cfg.anaparameter = 'anatomy'; - else - cfg.anaparameter = []; - end -end - -% set the common defaults for the functional data -cfg.funcolormap = ft_getopt(cfg, 'funcolormap', 'auto'); -cfg.funcolorlim = ft_getopt(cfg, 'funcolorlim', 'auto'); - -% set the common defaults for the statistical data -cfg.opacitymap = ft_getopt(cfg, 'opacitymap', 'auto'); -cfg.opacitylim = ft_getopt(cfg, 'opacitylim', 'auto'); -cfg.roi = ft_getopt(cfg, 'roi', []); - -% set the defaults per method - -% ortho -cfg.location = ft_getopt(cfg, 'location', 'auto'); -cfg.locationcoordinates = ft_getopt(cfg, 'locationcoordinates', 'head'); -cfg.crosshair = ft_getopt(cfg, 'crosshair', 'yes'); -cfg.colorbar = ft_getopt(cfg, 'colorbar', 'yes'); -cfg.axis = ft_getopt(cfg, 'axis', 'on'); -cfg.queryrange = ft_getopt(cfg, 'queryrange', 3); - -if isfield(cfg, 'TTlookup'), - error('TTlookup is old; now specify cfg.atlas, see help!'); -end - -% slice -cfg.nslices = ft_getopt(cfg, 'nslices', 20); -cfg.slicedim = ft_getopt(cfg, 'slicedim', 3); -cfg.slicerange = ft_getopt(cfg, 'slicerange', 'auto'); - -% surface -cfg.downsample = ft_getopt(cfg, 'downsample', 1); -cfg.surfdownsample = ft_getopt(cfg, 'surfdownsample', 1); -cfg.surffile = ft_getopt(cfg, 'surffile', 'surface_white_both.mat'); % use a triangulation that corresponds with the collin27 anatomical template in MNI coordinates -cfg.surfinflated = ft_getopt(cfg, 'surfinflated', []); -cfg.sphereradius = ft_getopt(cfg, 'sphereradius', []); -cfg.projvec = ft_getopt(cfg, 'projvec', 1); -cfg.projweight = ft_getopt(cfg, 'projweight', ones(size(cfg.projvec))); -cfg.projcomb = ft_getopt(cfg, 'projcomb', 'mean'); % or max -cfg.projthresh = ft_getopt(cfg, 'projthresh', []); -cfg.projmethod = ft_getopt(cfg, 'projmethod', 'nearest'); -cfg.distmat = ft_getopt(cfg, 'distmat', []); -cfg.camlight = ft_getopt(cfg, 'camlight', 'yes'); -cfg.renderer = ft_getopt(cfg, 'renderer', 'opengl'); -% if isequal(cfg.method,'surface') -% if ~isfield(cfg, 'projmethod'), -% error('specify cfg.projmethod'); -% end -% end - -% for backward compatibility -if strcmp(cfg.location, 'interactive') - cfg.location = 'auto'; -end - -% select the functional and the mask parameter -cfg.funparameter = parameterselection(cfg.funparameter, functional); -cfg.maskparameter = parameterselection(cfg.maskparameter, functional); -% only a single parameter should be selected -try, cfg.funparameter = cfg.funparameter{1}; end -try, cfg.maskparameter = cfg.maskparameter{1}; end - -if isvolume && cfg.downsample~=1 - % optionally downsample the anatomical and/or functional volumes - tmpcfg = keepfields(cfg, {'downsample'}); - tmpcfg.parameter = {cfg.funparameter, cfg.maskparameter, cfg.anaparameter}; - functional = ft_volumedownsample(tmpcfg, functional); - [cfg, functional] = rollback_provenance(cfg, functional); -end + % set the defaults for all methods + cfg.funparameter = ft_getopt(cfg, 'funparameter', []); + cfg.maskparameter = ft_getopt(cfg, 'maskparameter', []); + + + if isfield(cfg, 'atlas') && ~isempty(cfg.atlas) + % the atlas lookup requires the specification of the coordsys + functional = ft_checkdata(functional, 'datatype', {'volume', 'source'}, 'feedback', 'yes', 'hasunit', 'yes', 'hascoordsys', 'yes'); + else + % check if the input functional is valid for this function, a coordsys is not directly needed + functional = ft_checkdata(functional, 'datatype', {'volume', 'source'}, 'feedback', 'yes', 'hasunit', 'yes'); + end + + % determine the type of functional + issource = ft_datatype(functional, 'source'); + isvolume = ft_datatype(functional, 'volume'); + + % set the defaults for all methods + cfg.method = ft_getopt(cfg, 'method', 'ortho'); + cfg.funparameter = ft_getopt(cfg, 'funparameter', []); + cfg.maskparameter = ft_getopt(cfg, 'maskparameter', []); + cfg.downsample = ft_getopt(cfg, 'downsample', 1); + cfg.title = ft_getopt(cfg, 'title', ''); + cfg.atlas = ft_getopt(cfg, 'atlas', []); + cfg.marker = ft_getopt(cfg, 'marker', []); + cfg.markersize = ft_getopt(cfg, 'markersize', 5); + cfg.markercolor = ft_getopt(cfg, 'markercolor', [1 1 1]); + + if ~isfield(cfg, 'anaparameter') + if isfield(functional, 'anatomy') + cfg.anaparameter = 'anatomy'; + else + cfg.anaparameter = []; + end + end + + % set the common defaults for the functional data + cfg.funcolormap = ft_getopt(cfg, 'funcolormap', 'auto'); + cfg.funcolorlim = ft_getopt(cfg, 'funcolorlim', 'auto'); + + % set the common defaults for the statistical data + cfg.opacitymap = ft_getopt(cfg, 'opacitymap', 'auto'); + cfg.opacitylim = ft_getopt(cfg, 'opacitylim', 'auto'); + cfg.roi = ft_getopt(cfg, 'roi', []); + + % set the defaults per method + + % ortho + cfg.location = ft_getopt(cfg, 'location', 'auto'); + cfg.locationcoordinates = ft_getopt(cfg, 'locationcoordinates', 'head'); + cfg.crosshair = ft_getopt(cfg, 'crosshair', 'yes'); + cfg.colorbar = ft_getopt(cfg, 'colorbar', 'yes'); + cfg.axis = ft_getopt(cfg, 'axis', 'on'); + cfg.queryrange = ft_getopt(cfg, 'queryrange', 3); + + if isfield(cfg, 'TTlookup'), + error('TTlookup is old; now specify cfg.atlas, see help!'); + end + + % slice + cfg.nslices = ft_getopt(cfg, 'nslices', 20); + cfg.slicedim = ft_getopt(cfg, 'slicedim', 3); + cfg.slicerange = ft_getopt(cfg, 'slicerange', 'auto'); + + % surface + cfg.downsample = ft_getopt(cfg, 'downsample', 1); + cfg.surfdownsample = ft_getopt(cfg, 'surfdownsample', 1); + cfg.surffile = ft_getopt(cfg, 'surffile', 'surface_white_both.mat'); % use a triangulation that corresponds with the collin27 anatomical template in MNI coordinates + cfg.surfinflated = ft_getopt(cfg, 'surfinflated', []); + cfg.sphereradius = ft_getopt(cfg, 'sphereradius', []); + cfg.projvec = ft_getopt(cfg, 'projvec', 1); + cfg.projweight = ft_getopt(cfg, 'projweight', ones(size(cfg.projvec))); + cfg.projcomb = ft_getopt(cfg, 'projcomb', 'mean'); % or max + cfg.projthresh = ft_getopt(cfg, 'projthresh', []); + cfg.projmethod = ft_getopt(cfg, 'projmethod', 'nearest'); + cfg.distmat = ft_getopt(cfg, 'distmat', []); + cfg.camlight = ft_getopt(cfg, 'camlight', 'yes'); + cfg.renderer = ft_getopt(cfg, 'renderer', 'opengl'); + % if isequal(cfg.method,'surface') + % if ~isfield(cfg, 'projmethod'), + % error('specify cfg.projmethod'); + % end + % end + + % for backward compatibility + if strcmp(cfg.location, 'interactive') + cfg.location = 'auto'; + end + + % ensure that old and unsupported options are not being relied on by the end-user's script + % instead of specifying cfg.coordsys, the user should specify the coordsys in the functional data + cfg = ft_checkconfig(cfg, 'forbidden', {'units', 'inputcoordsys', 'coordinates'}); + cfg = ft_checkconfig(cfg, 'deprecated', 'coordsys'); -%%% make the local variables: -if isfield(functional, 'dim') - dim = functional.dim; -else - dim = [size(functional.pos,1) 1]; -end + cfg.title = ft_getopt(cfg, 'title', ''); + cfg.atlas = ft_getopt(cfg, 'atlas', []); + cfg.topoplot = ft_getopt(cfg, 'topoplot', ''); -hasatlas = ~isempty(cfg.atlas); -if hasatlas - if ischar(cfg.atlas) - % initialize the atlas - [p, f, x] = fileparts(cfg.atlas); - fprintf(['reading ', f,' atlas coordinates and labels\n']); - atlas = ft_read_atlas(cfg.atlas); - else - atlas = cfg.atlas; - end -end -hasroi = ~isempty(cfg.roi); -if hasroi - if ~hasatlas - error('specify cfg.atlas which belongs to cfg.roi') - else - % get the mask - tmpcfg = []; - tmpcfg.roi = cfg.roi; - tmpcfg.atlas = cfg.atlas; - tmpcfg.inputcoord = functional.coordsys; - roi = ft_volumelookup(tmpcfg,functional); - end -end - -% %%% anaparameter -hasana = 1; % by definition, you's got ana if you're using this function :-) -% if isempty(cfg.anaparameter); -% hasana = 0; -% fprintf('not plotting anatomy\n'); -% elseif isfield(functional, cfg.anaparameter) -% hasana = 1; -% ana = getsubfield(functional, cfg.anaparameter); -% % convert integers to single precision float if neccessary -% if isa(ana, 'uint8') || isa(ana, 'uint16') || isa(ana, 'int8') || isa(ana, 'int16') -% fprintf('converting anatomy to double\n'); -% ana = double(ana); -% end -% fprintf('scaling anatomy to [0 1]\n'); -% dmin = min(ana(:)); -% dmax = max(ana(:)); -% ana = (ana-dmin)./(dmax-dmin); -% else -% warning('do not understand cfg.anaparameter, not plotting anatomy\n') -% hasana = 0; -% end + %% start building the figure + global st + + % things get messy if there's an SPM window already open + spmfigh = spm_figure('FindWin'); + if(~isempty(spmfigh)) + close(spmfigh) + end + + spm_image('init',cfg.mripath); % load/reload structural MRI + nmt_spmfig_setup(cfg); + -%%% funparameter -% has fun? -if ~isempty(cfg.funparameter) - if issubfield(functional, cfg.funparameter) - hasfun = 1; - tmpfun = getsubfield(functional, cfg.funparameter); - else - error('cfg.funparameter not found in functional'); - end +% if funparameter is a simple string, convert to 1-element cell +if iscell(cfg.funparameter) + funparameters = cfg.funparameter; else - hasfun = 0; - fprintf('no functional parameter\n'); + funparameters{1} = cfg.funparameter; end -% handle the dimensions of functional data - -if hasfun - dimord = getdimord(functional, cfg.funparameter); - dimtok = tokenize(dimord, '_'); - - if strcmp(dimtok{1}, '{pos}') - tmpdim = getdimsiz(functional, cfg.funparameter); - fun = nan(tmpdim); - insideindx = find(functional.inside); - - tmpfun = cell2mat(tmpfun); - switch(dimord) +for funidx = 1:length(funparameters) + cfg.funparameter = funparameters{funidx} + % ensure that old and unsupported options are not being relied on by the end-user's script + % instead of specifying cfg.coordsys, the user should specify the coordsys in the functional data + cfg = ft_checkconfig(cfg, 'renamedval', {'funparameter', 'avg.pow', 'pow'}); + cfg = ft_checkconfig(cfg, 'renamedval', {'funparameter', 'avg.coh', 'coh'}); + cfg = ft_checkconfig(cfg, 'renamedval', {'funparameter', 'avg.mom', 'mom'}); + cfg = ft_checkconfig(cfg, 'renamedval', {'funparameter', 'avg.aa', 'aa'}); + cfg = ft_checkconfig(cfg, 'renamedval', {'funparameter', 'avg.itc', 'itc'}); + cfg = ft_checkconfig(cfg, 'renamedval', {'funparameter', 'avg.tf', 'tf'}); + cfg = ft_checkconfig(cfg, 'renamedval', {'maskparameter', 'avg.pow', 'pow'}); + cfg = ft_checkconfig(cfg, 'renamedval', {'maskparameter', 'avg.coh', 'coh'}); + cfg = ft_checkconfig(cfg, 'renamedval', {'maskparameter', 'avg.mom', 'mom'}); + cfg = ft_checkconfig(cfg, 'renamedval', {'maskparameter', 'avg.aa', 'aa'}); + cfg = ft_checkconfig(cfg, 'renamedval', {'maskparameter', 'avg.itc', 'itc'}); + cfg = ft_checkconfig(cfg, 'renamedval', {'maskparameter', 'avg.tf', 'tf'}); + + + + % select the functional and the mask parameter + cfg.funparameter = parameterselection(cfg.funparameter, functional); + cfg.maskparameter = parameterselection(cfg.maskparameter, functional); + % only a single parameter should be selected + try, cfg.funparameter = cfg.funparameter{1}; end + try, cfg.maskparameter = cfg.maskparameter{1}; end + + switch(getdimord(functional,cfg.funparameter)) case '{pos}_freq_time' - tmpfun = reshape(tmpfun,[size(tmpfun,1)/length(insideindx) length(insideindx) size(tmpfun,2)]); - tmpfun = permute(tmpfun, [2 1 3]); + cfg.plottype = ft_getopt(cfg, 'plottype', 'tf'); % default plot type is "time-freq" + otherwise + cfg.plottype = ft_getopt(cfg, 'plottype', 'ts'); % default plot type is "time series" end - fun(insideindx,:,:) = tmpfun; % replace the cell-array functional with a normal array - clear tmpfun; - dimtok{1} = 'pos'; % update the description of the dimensions - dimord([1 5]) = []; % remove the { and } - else - fun = tmpfun; - clear tmpfun; - end - - - if strcmp(dimord, 'pos_rgb') - % treat functional data as rgb values - if any(fun(:)>1 | fun(:)<0) - % scale - tmpdim = size(fun); - nvox = prod(tmpdim(1:end-1)); - tmpfun = reshape(fun,[nvox tmpdim(end)]); - m1 = max(tmpfun,[],1); - m2 = min(tmpfun,[],1); - tmpfun = (tmpfun-m2(ones(nvox,1),:))./(m1(ones(nvox,1),:)-m2(ones(nvox,1),:)); - fun = reshape(tmpfun, tmpdim); - clear tmpfun + + if isvolume && cfg.downsample~=1 + % optionally downsample the anatomical and/or functional volumes + tmpcfg = keepfields(cfg, {'downsample'}); + tmpcfg.parameter = {cfg.funparameter, cfg.maskparameter, cfg.anaparameter}; + functional = ft_volumedownsample(tmpcfg, functional); + [cfg, functional] = rollback_provenance(cfg, functional); end - qi = 1; - hasfreq = 0; - hastime = 0; - - doimage = 1; - fcolmin = 0; - fcolmax = 1; - - else - % determine scaling min and max (fcolmin fcolmax) and funcolormap - if ~isa(fun, 'logical') - funmin = min(fun(:)); - funmax = max(fun(:)); + + %%% make the local variables: + if isfield(functional, 'dim') + dim = functional.dim; else - funmin = 0; - funmax = 1; - end - % smart automatic limits - if isequal(cfg.funcolorlim,'auto') - if sign(funmin)>-1 && sign(funmax)>-1 - cfg.funcolorlim = 'zeromax'; - elseif sign(funmin)<1 && sign(funmax)<1 - cfg.funcolorlim = 'minzero'; - else - cfg.funcolorlim = 'maxabs'; - end + dim = [size(functional.pos,1) 1]; end - if ischar(cfg.funcolorlim) - % limits are given as string - if isequal(cfg.funcolorlim,'maxabs') - fcolmin = -max(abs([funmin,funmax])); - fcolmax = max(abs([funmin,funmax])); - if isequal(cfg.funcolormap,'auto'); cfg.funcolormap = 'default'; end; - elseif isequal(cfg.funcolorlim,'zeromax') - fcolmin = 0; - fcolmax = funmax; - if isequal(cfg.funcolormap,'auto'); cfg.funcolormap = 'hot'; end; - elseif isequal(cfg.funcolorlim,'minzero') - fcolmin = funmin; - fcolmax = 0; - if isequal(cfg.funcolormap,'auto'); cfg.funcolormap = 'cool'; end; - else - error('do not understand cfg.funcolorlim'); - end - else - % limits are numeric - fcolmin = cfg.funcolorlim(1); - fcolmax = cfg.funcolorlim(2); - % smart colormap - if isequal(cfg.funcolormap,'auto') - if sign(fcolmin) == -1 && sign(fcolmax) == 1 - cfg.funcolormap = 'default'; + + hasatlas = ~isempty(cfg.atlas); + if hasatlas + if ischar(cfg.atlas) + % initialize the atlas + [p, f, x] = fileparts(cfg.atlas); + fprintf(['reading ', f,' atlas coordinates and labels\n']); + atlas = ft_read_atlas(cfg.atlas); else - if fcolmin < 0 - cfg.funcolormap = 'cool'; - else - cfg.funcolormap = 'hot'; - end + atlas = cfg.atlas; end - end - end % if ischar - clear funmin funmax - - % FIXME should this not be done earlier in the code? - % ensure that the functional data is real - if ~isreal(fun) - warning('functional data is complex, taking absolute value'); - fun = abs(fun); end - if ndims(fun)>3 || prod(dim)==size(fun,1) - if strcmp(dimord, 'pos_freq_time') - % functional contains time-frequency representation - qi = [1 1]; - hasfreq = numel(functional.freq)>1; - hastime = numel(functional.time)>1; - %fun = reshape(fun, [dim numel(functional.freq) numel(functional.time)]); - - fun = permute(fun,[1 3 2]); % reorder to pos_time_freq - dimord = 'pos_time_freq'; - elseif strcmp(dimord, 'pos_time') - % functional contains evoked field - qi = 1; - hasfreq = 0; - hastime = numel(functional.time)>1; - fun = squeeze(fun); -% fun = reshape(fun, [dim numel(functional.time)]); - elseif strcmp(dimord, 'pos_freq') - % functional contains frequency spectra - qi = 1; - hasfreq = numel(functional.freq)>1; - hastime = 0; - fun = reshape(fun, [dim numel(functional.freq)]); - else - qi = 1; - hasfreq = 0; - hastime = 0; - fun = reshape(fun, dim); - end - else - % do nothing - qi = 1; - hasfreq = 0; - hastime = 0; + hasroi = ~isempty(cfg.roi); + if hasroi + if ~hasatlas + error('specify cfg.atlas which belongs to cfg.roi') + else + % get the mask + tmpcfg = []; + tmpcfg.roi = cfg.roi; + tmpcfg.atlas = cfg.atlas; + tmpcfg.inputcoord = functional.coordsys; + roi = ft_volumelookup(tmpcfg,functional); + end end - doimage = 0; - end % if dimord has rgb or something else -else - % there is no functional data - qi = 1; - hasfreq = 0; - hastime = 0; - - doimage = 0; - fcolmin = 0; % needs to be defined for callback - fcolmax = 1; -end % handle fun - -%%% maskparameter -% has mask? -if ~isempty(cfg.maskparameter) - if issubfield(functional, cfg.maskparameter) - if ~hasfun - error('you can not have a mask without functional data') - else - hasmsk = 1; - msk = getsubfield(functional, cfg.maskparameter); - if islogical(msk) % otherwise sign() not posible - msk = double(msk); - end - end - else - error('cfg.maskparameter not found in functional'); - end -else - hasmsk = 0; - fprintf('no masking parameter\n'); -end - -% handle mask -if hasmsk - % reshape to match fun - if strcmp(dimord, 'pos_time_freq') - % functional contains timefrequency representation - msk = permute(msk,[1 3 2]); % reorder to pos_time_freq - msk = reshape(msk, [dim numel(functional.time) numel(functional.freq)]); - elseif strcmp(dimord, 'pos_time') - % functional contains evoked field - msk = reshape(msk, [dim numel(functional.time)]); - elseif strcmp(dimord, 'pos_freq') - % functional contains frequency spectra - msk = reshape(msk, [dim numel(functional.freq)]); - else - msk = reshape(msk, dim); - end - - % determine scaling and opacitymap - mskmin = min(msk(:)); - mskmax = max(msk(:)); - % determine the opacity limits and the opacity map - % smart limits: make from auto other string, or equal to funcolorlim if funparameter == maskparameter - if isequal(cfg.opacitylim,'auto') - if isequal(cfg.funparameter,cfg.maskparameter) - cfg.opacitylim = cfg.funcolorlim; - else - if sign(mskmin)>-1 && sign(mskmax)>-1 - cfg.opacitylim = 'zeromax'; - elseif sign(mskmin)<1 && sign(mskmax)<1 - cfg.opacitylim = 'minzero'; - else - cfg.opacitylim = 'maxabs'; - end - end - end - if ischar(cfg.opacitylim) - % limits are given as string - switch cfg.opacitylim - case 'zeromax' - opacmin = 0; - opacmax = mskmax; - if isequal(cfg.opacitymap,'auto'), cfg.opacitymap = 'rampup'; end; - case 'minzero' - opacmin = mskmin; - opacmax = 0; - if isequal(cfg.opacitymap,'auto'), cfg.opacitymap = 'rampdown'; end; - case 'maxabs' - opacmin = -max(abs([mskmin, mskmax])); - opacmax = max(abs([mskmin, mskmax])); - if isequal(cfg.opacitymap,'auto'), cfg.opacitymap = 'vdown'; end; - otherwise - error('incorrect specification of cfg.opacitylim'); - end % switch opacitylim - else - % limits are numeric - opacmin = cfg.opacitylim(1); - opacmax = cfg.opacitylim(2); - if isequal(cfg.opacitymap,'auto') - if sign(opacmin)>-1 && sign(opacmax)>-1 - cfg.opacitymap = 'rampup'; - elseif sign(opacmin)<1 && sign(opacmax)<1 - cfg.opacitymap = 'rampdown'; - else - cfg.opacitymap = 'vdown'; - end - end - end % handling opacitylim and opacitymap - clear mskmin mskmax -else - opacmin = []; - opacmax = []; -end - -% prevent outside fun from being plotted -if hasfun && isfield(functional,'inside') && ~hasmsk - hasmsk = 1; - msk = ones(size(fun)); - cfg.opacitymap = 'rampup'; - opacmin = 0; - opacmax = 1; - % make intelligent mask - if isequal(cfg.method,'surface') - msk(functional.inside) = 1; - else - if hasana - msk(functional.inside) = 0.5; % so anatomy is visible + % %%% anaparameter + hasana = 1; % by definition, you's got ana if you're using this function :-) + % if isempty(cfg.anaparameter); + % hasana = 0; + % fprintf('not plotting anatomy\n'); + % elseif isfield(functional, cfg.anaparameter) + % hasana = 1; + % ana = getsubfield(functional, cfg.anaparameter); + % % convert integers to single precision float if neccessary + % if isa(ana, 'uint8') || isa(ana, 'uint16') || isa(ana, 'int8') || isa(ana, 'int16') + % fprintf('converting anatomy to double\n'); + % ana = double(ana); + % end + % fprintf('scaling anatomy to [0 1]\n'); + % dmin = min(ana(:)); + % dmax = max(ana(:)); + % ana = (ana-dmin)./(dmax-dmin); + % else + % warning('do not understand cfg.anaparameter, not plotting anatomy\n') + % hasana = 0; + % end + + %%% funparameter + % has fun? + if ~isempty(cfg.funparameter) + if issubfield(functional, cfg.funparameter) + hasfun = 1; + tmpfun = getsubfield(functional, cfg.funparameter); + else + error('cfg.funparameter not found in functional'); + end else - msk(functional.inside) = 1; + hasfun = 0; + fprintf('no functional parameter\n'); end - end -end - -% if region of interest is specified, mask everything besides roi -if hasfun && hasroi && ~hasmsk - hasmsk = 1; - msk = roi; - cfg.opacitymap = 'rampup'; - opacmin = 0; - opacmax = 1; -elseif hasfun && hasroi && hasmsk - msk = roi .* msk; - opacmin = []; - opacmax = []; % has to be defined -elseif hasroi - error('you can not have a roi without functional data') -end - -%% start building the figure -global st - -% things get messy if there's an SPM window already open -spmfigh = spm_figure('FindWin'); -if(~isempty(spmfigh)) - close(spmfigh) -end - -spm_image('init',cfg.mripath); % load/reload structural MRI -nmt_spmfig_setup(cfg); - -%%% set color and opacity mapping for this figure -if hasfun - colormap(cfg.funcolormap); - cfg.funcolormap = colormap; -end -if hasmsk - cfg.opacitymap = alphamap(cfg.opacitymap); - alphamap(cfg.opacitymap); - if ndims(fun)>3 && ndims(msk)==3 - siz = size(fun); - msk = repmat(msk, [1 1 1 siz(4:end)]); - end -end - - -%% ********************************************************************************* -% SPM8 expects everything in mm -functional = ft_convert_units(functional,'mm'); - - -if ~isempty(cfg.funparameter) - if issubfield(functional, cfg.funparameter) - hasfun = 1; - - st.nmt.pos = functional.pos; - -% getsubfield(functional, cfg.funparameter); -% if(iscell(fun)) -% st.nmt.fun{1} = cell2mat(st.nmt.fun{1}); -% end - -% switch(cfg.funparameter) -% case {'mom','itc'} - - st.nmt.fun{1} = fun; - clear fun; + + % handle the dimensions of functional data + + if hasfun + dimord = getdimord(functional, cfg.funparameter); + dimtok = tokenize(dimord, '_'); - if(hastime & ~hasfreq) - % voxels x time - st.nmt.time = functional.time; - if(isfield(functional,'freqbands')) - st.nmt.freq = functional.freqbands; - else - st.nmt.freq = [0 inf]; - end - - if(~isfield(cfg,'time') & ~isfield(cfg,'vox')) - [~,peakind] = max(abs(st.nmt.fun{1}(:))); - [peakvox_idx,peaktime_idx] = ind2sub(size(st.nmt.fun{1}),peakind); - cfg.time_idx(1) = peaktime_idx; - cfg.vox_idx = peakvox_idx; - end + if strcmp(dimtok{1}, '{pos}') + tmpdim = getdimsiz(functional, cfg.funparameter); + fun = nan(tmpdim); + insideindx = find(functional.inside); - if(~isfield(cfg,'time') & isfield(cfg,'vox')) - [~,peaktime_idx] = max(abs(st.nmt.fun{1}(cfg.vox_idx,:))); - cfg.time_idx(1) = peaktime_idx; + tmpfun = cell2mat(tmpfun); + switch(dimord) + case {'{pos}_freq_time','{pos}_ori_time','{pos}_unknown_time'} % ori is hack... FT thinks that 3 freq bands is an ori! + tmpfun = reshape(tmpfun,[size(tmpfun,1)/length(insideindx) length(insideindx) size(tmpfun,2)]); + tmpfun = permute(tmpfun, [2 1 3]); end - if(isfield(cfg,'time') & ~isfield(cfg,'vox')) - [~,peakvox_idx] = max(abs(st.nmt.fun{1}(cfg.time_idx,:))); - cfg.vox_idx = peakvox_idx; - end + % tmpfun = real(tmpfun); + % tmpfun = (tmpfun ./ repmat(mean(abs(tmpfun(:,:,1:200)),3),1,1,476)); - % move MRI crosshairs to desired/peak voxel - spm_orthviews('Reposition',st.nmt.pos(cfg.vox_idx,:)) + % tmpfun = sum(tmpfun,2); + % fun = fun(insideindx,1,:); - if(length(cfg.time_idx(1)) == 1) - cfg.time_idx(2) = cfg.time_idx(1); + fun(insideindx,:,:) = tmpfun; % replace the cell-array functional with a normal array + clear tmpfun; + dimtok{1} = 'pos'; % update the description of the dimensions + dimord([1 5]) = []; % remove the { and } + else + fun = tmpfun; + clear tmpfun; + end + + + if strcmp(dimord, 'pos_rgb') + % treat functional data as rgb values + if any(fun(:)>1 | fun(:)<0) + % scale + tmpdim = size(fun); + nvox = prod(tmpdim(1:end-1)); + tmpfun = reshape(fun,[nvox tmpdim(end)]); + m1 = max(tmpfun,[],1); + m2 = min(tmpfun,[],1); + tmpfun = (tmpfun-m2(ones(nvox,1),:))./(m1(ones(nvox,1),:)-m2(ones(nvox,1),:)); + fun = reshape(tmpfun, tmpdim); + clear tmpfun end + qi = 1; + hasfreq = 0; + hastime = 0; - cfg.freq_idx = [1 1]; - elseif(hastime & hasfreq) - % voxels x frequency x time - st.nmt.time = functional.time; - st.nmt.freq = functional.freqbands; + doimage = 1; + fcolmin = 0; + fcolmax = 1; - if(~isfield(cfg,'time') & ~isfield(cfg,'vox')) - [~,peakind] = max(abs(st.nmt.fun{1}(:))); - [peakvox_idx,peaktime_idx,peakfreq_idx] = ind2sub(size(st.nmt.fun{1}),peakind); - cfg.time_idx(1) = peaktime_idx; - cfg.freq_idx(1) = peakfreq_idx; - cfg.vox_idx = peakvox_idx; + else + % determine scaling min and max (fcolmin fcolmax) and funcolormap + if ~isa(fun, 'logical') + funmin = min(fun(:)); + funmax = max(fun(:)); + else + funmin = 0; + funmax = 1; end - - if(~isfield(cfg,'time') & isfield(cfg,'vox')) - [~,peaktime_idx] = max(abs(st.nmt.fun{1}(cfg.vox_idx,:))); - cfg.time_idx(1) = peaktime_idx; + % smart automatic limits + if isequal(cfg.funcolorlim,'auto') + if sign(funmin)>-1 && sign(funmax)>-1 + cfg.funcolorlim = 'zeromax'; + elseif sign(funmin)<1 && sign(funmax)<1 + cfg.funcolorlim = 'minzero'; + else + cfg.funcolorlim = 'maxabs'; + end end + if ischar(cfg.funcolorlim) + % limits are given as string + if isequal(cfg.funcolorlim,'maxabs') + fcolmin = -max(abs([funmin,funmax])); + fcolmax = max(abs([funmin,funmax])); + if isequal(cfg.funcolormap,'auto'); cfg.funcolormap = 'default'; end; + elseif isequal(cfg.funcolorlim,'zeromax') + fcolmin = 0; + fcolmax = funmax; + if isequal(cfg.funcolormap,'auto'); cfg.funcolormap = 'hot'; end; + elseif isequal(cfg.funcolorlim,'minzero') + fcolmin = funmin; + fcolmax = 0; + if isequal(cfg.funcolormap,'auto'); cfg.funcolormap = 'cool'; end; + else + error('do not understand cfg.funcolorlim'); + end + else + % limits are numeric + fcolmin = cfg.funcolorlim(1); + fcolmax = cfg.funcolorlim(2); + % smart colormap + if isequal(cfg.funcolormap,'auto') + if sign(fcolmin) == -1 && sign(fcolmax) == 1 + cfg.funcolormap = 'default'; + else + if fcolmin < 0 + cfg.funcolormap = 'cool'; + else + cfg.funcolormap = 'hot'; + end + end + end + end % if ischar + clear funmin funmax - if(isfield(cfg,'time') & ~isfield(cfg,'vox')) - [~,peakvox_idx] = max(abs(st.nmt.fun{1}(cfg.time_idx,:))); - cfg.vox_idx = peakvox_idx; + % FIXME should this not be done earlier in the code? + % ensure that the functional data is real + if ~isreal(fun) + warning('functional data is complex, taking absolute value'); + fun = abs(fun); end - % move MRI crosshairs to desired/peak voxel - spm_orthviews('Reposition',st.nmt.pos(cfg.vox_idx,:)) - - if(length(cfg.time_idx(1)) == 1) - cfg.time_idx(2) = cfg.time_idx(1); + if ndims(fun)>3 || prod(dim)==size(fun,1) + if strcmp(dimord, 'pos_freq_time') | strcmp(dimord, 'pos_ori_time') + % functional contains time-frequency representation + qi = [1 1]; + hasfreq = numel(functional.freq)>1; + hastime = numel(functional.time)>1; + %fun = reshape(fun, [dim numel(functional.freq) numel(functional.time)]); + + fun = permute(fun,[1 3 2]); % reorder to pos_time_freq + dimord = 'pos_time_freq'; + elseif strcmp(dimord, 'pos_time') + % functional contains evoked field + qi = 1; + hasfreq = 0; + hastime = numel(functional.time)>1; + fun = squeeze(fun); + % fun = reshape(fun, [dim numel(functional.time)]); + elseif strcmp(dimord, 'pos_freq') + % functional contains frequency spectra + qi = 1; + hasfreq = numel(functional.freq)>1; + hastime = 0; + fun = reshape(fun, [dim numel(functional.freq)]); + else + qi = 1; + hasfreq = 0; + hastime = 0; + fun = reshape(fun, dim); + end + else + % do nothing + qi = 1; + hasfreq = 0; + hastime = 0; end - if(length(cfg.freq_idx(1)) == 1) - cfg.freq_idx(2) = cfg.freq_idx(1); + + doimage = 0; + end % if dimord has rgb or something else + else + % there is no functional data + qi = 1; + hasfreq = 0; + hastime = 0; + + doimage = 0; + fcolmin = 0; % needs to be defined for callback + fcolmax = 1; + end % handle fun + + %%% maskparameter + % has mask? + if ~isempty(cfg.maskparameter) + if issubfield(functional, cfg.maskparameter) + if ~hasfun + error('you can not have a mask without functional data') + else + hasmsk = 1; + msk = getsubfield(functional, cfg.maskparameter); + if islogical(msk) % otherwise sign() not posible + msk = double(msk); + end end else - cfg.time_idx = [1 1]; % no time dimension in this case, e.g., 'pow' - cfg.freq_idx = [1 1]; % frequency dimension is singleton in this case - - st.nmt.freq = [0 inf]; % dummy frequencies to make later functions happy + error('cfg.maskparameter not found in functional'); + end + else + hasmsk = 0; + fprintf('no masking parameter\n'); + end + + % handle mask + if hasmsk + % reshape to match fun + if strcmp(dimord, 'pos_time_freq') + % functional contains timefrequency representation + msk = permute(msk,[1 3 2]); % reorder to pos_time_freq + msk = reshape(msk, [dim numel(functional.time) numel(functional.freq)]); + elseif strcmp(dimord, 'pos_time') + % functional contains evoked field + msk = reshape(msk, [dim numel(functional.time)]); + elseif strcmp(dimord, 'pos_freq') + % functional contains frequency spectra + msk = reshape(msk, [dim numel(functional.freq)]); + else + msk = reshape(msk, dim); end - set(st.nmt.gui.f1,'String',num2str(st.nmt.freq(:,1))); - set(st.nmt.gui.f2,'String',num2str(st.nmt.freq(:,2))); - - st.nmt.cfg = cfg; - st.nmt.msk = reshape(msk,size(st.nmt.fun{1})); - + % determine scaling and opacitymap + mskmin = min(msk(:)); + mskmax = max(msk(:)); + % determine the opacity limits and the opacity map + % smart limits: make from auto other string, or equal to funcolorlim if funparameter == maskparameter + if isequal(cfg.opacitylim,'auto') + if isequal(cfg.funparameter,cfg.maskparameter) + cfg.opacitylim = cfg.funcolorlim; + else + if sign(mskmin)>-1 && sign(mskmax)>-1 + cfg.opacitylim = 'zeromax'; + elseif sign(mskmin)<1 && sign(mskmax)<1 + cfg.opacitylim = 'minzero'; + else + cfg.opacitylim = 'maxabs'; + end + end + end + if ischar(cfg.opacitylim) + % limits are given as string + switch cfg.opacitylim + case 'zeromax' + opacmin = 0; + opacmax = mskmax; + if isequal(cfg.opacitymap,'auto'), cfg.opacitymap = 'rampup'; end; + case 'minzero' + opacmin = mskmin; + opacmax = 0; + if isequal(cfg.opacitymap,'auto'), cfg.opacitymap = 'rampdown'; end; + case 'maxabs' + opacmin = -max(abs([mskmin, mskmax])); + opacmax = max(abs([mskmin, mskmax])); + if isequal(cfg.opacitymap,'auto'), cfg.opacitymap = 'vdown'; end; + otherwise + error('incorrect specification of cfg.opacitylim'); + end % switch opacitylim + else + % limits are numeric + opacmin = cfg.opacitylim(1); + opacmax = cfg.opacitylim(2); + if isequal(cfg.opacitymap,'auto') + if sign(opacmin)>-1 && sign(opacmax)>-1 + cfg.opacitymap = 'rampup'; + elseif sign(opacmin)<1 && sign(opacmax)<1 + cfg.opacitymap = 'rampdown'; + else + cfg.opacitymap = 'vdown'; + end + end + end % handling opacitylim and opacitymap + clear mskmin mskmax else - error('cfg.funparameter not found in functional'); + opacmin = []; + opacmax = []; + end + + % prevent outside fun from being plotted + if hasfun && isfield(functional,'inside') && ~hasmsk + hasmsk = 1; + msk = ones(size(fun)); + cfg.opacitymap = 'rampup'; + opacmin = 0; + opacmax = 1; + % make intelligent mask + if isequal(cfg.method,'surface') + msk(functional.inside) = 1; + else + if hasana + msk(functional.inside) = 0.5; % so anatomy is visible + else + msk(functional.inside) = 1; + end + end + end + + % if region of interest is specified, mask everything besides roi + if hasfun && hasroi && ~hasmsk + hasmsk = 1; + msk = roi; + cfg.opacitymap = 'rampup'; + opacmin = 0; + opacmax = 1; + elseif hasfun && hasroi && hasmsk + msk = roi .* msk; + opacmin = []; + opacmax = []; % has to be defined + elseif hasroi + error('you can not have a roi without functional data') + end + + + + %%% set color and opacity mapping for this figure + if hasfun + colormap(cfg.funcolormap); + cfg.funcolormap = colormap; + end + if hasmsk + cfg.opacitymap = alphamap(cfg.opacitymap); + alphamap(cfg.opacitymap); + if ndims(fun)>3 && ndims(msk)==3 + siz = size(fun); + msk = repmat(msk, [1 1 1 siz(4:end)]); + end + end + + if(~isfield(cfg,'colormap')) + cfg.colormap = jet(64); + elseif(ischar(cfg.colormap)) + cfg.colormap = feval(cfg.colormap,64); end -else - hasfun = 0; - fprintf('no functional parameter\n'); -end - -switch(cfg.topoplot) - case 'timelock' - st.nmt.timelock = functional.timelock; - case {'spatialfilter'} - st.nmt.spatialfilter = functional.filter; - case {'leadfield','leadfieldX','leadfieldY','leadfieldZ','leadfieldori'} - st.nmt.grid = functional.grid; -end -cfg.axsel = 1; -nmt_spm_plot(cfg); -nmt_image; + %% ********************************************************************************* + % SPM8 expects everything in mm + functional = ft_convert_units(functional,'mm'); + + + if ~isempty(cfg.funparameter) + if issubfield(functional, cfg.funparameter) + hasfun = 1; + + st.nmt.pos = functional.pos; + + st.nmt.fun{funidx} = fun; + clear fun; + + if(hastime & ~hasfreq) + % voxels x time + st.nmt.time = functional.time; + if(isfield(functional,'freqbands')) + st.nmt.freq = functional.freqbands; + else + st.nmt.freq = [0 inf]; + end + + if(~isfield(cfg,'time') & ~isfield(cfg,'vox')) + [~,peakind] = max(abs(st.nmt.fun{funidx}(:))); + [peakvox_idx,peaktime_idx] = ind2sub(size(st.nmt.fun{funidx}),peakind); + cfg.time_idx(1) = peaktime_idx; + cfg.vox_idx = peakvox_idx; + end + + if(~isfield(cfg,'time') & isfield(cfg,'vox')) + [~,peaktime_idx] = max(abs(st.nmt.fun{funidx}(cfg.vox_idx,:))); + cfg.time_idx(1) = peaktime_idx; + end + + if(isfield(cfg,'time') & ~isfield(cfg,'vox')) + [~,peakvox_idx] = max(abs(st.nmt.fun{funidx}(cfg.time_idx,:))); + cfg.vox_idx = peakvox_idx; + end + + % move MRI crosshairs to desired/peak voxel + spm_orthviews('Reposition',st.nmt.pos(cfg.vox_idx,:)) + + if(length(cfg.time_idx(1)) == 1) + cfg.time_idx(2) = cfg.time_idx(1); + end + + cfg.freq_idx = [1 1]; + elseif(hastime & hasfreq) + % voxels x frequency x time + st.nmt.time = functional.time; + st.nmt.freq = functional.freqbands; + + if(~isfield(cfg,'time') & ~isfield(cfg,'vox')) + [~,peakind] = max(abs(st.nmt.fun{funidx}(:))); + [peakvox_idx,peaktime_idx,peakfreq_idx] = ind2sub(size(st.nmt.fun{funidx}),peakind); + cfg.time_idx(1) = peaktime_idx; + cfg.freq_idx(1) = peakfreq_idx; + cfg.vox_idx = peakvox_idx; + end + + if(~isfield(cfg,'time') & isfield(cfg,'vox')) + [~,peaktime_idx] = max(abs(st.nmt.fun{funidx}(cfg.vox_idx,:))); + cfg.time_idx(1) = peaktime_idx; + end + + if(isfield(cfg,'time') & ~isfield(cfg,'vox')) + [~,peakvox_idx] = max(abs(st.nmt.fun{funidx}(cfg.time_idx,:))); + cfg.vox_idx = peakvox_idx; + end + + % move MRI crosshairs to desired/peak voxel + spm_orthviews('Reposition',st.nmt.pos(cfg.vox_idx,:)) + + if(length(cfg.time_idx(1)) == 1) + cfg.time_idx(2) = cfg.time_idx(1); + end + if(length(cfg.freq_idx(1)) == 1) + cfg.freq_idx(2) = cfg.freq_idx(1); + end + else + cfg.time_idx = [1 1]; % no time dimension in this case, e.g., 'pow' + cfg.freq_idx = [1 1]; % frequency dimension is singleton in this case + + st.nmt.freq = [0 inf]; % dummy frequencies to make later functions happy + end + + set(st.nmt.gui.f1,'String',num2str(st.nmt.freq(:,1))); + set(st.nmt.gui.f2,'String',num2str(st.nmt.freq(:,2))); + + st.nmt.cfg = cfg; + st.nmt.msk = reshape(msk,size(st.nmt.fun{funidx})); + + else + error('cfg.funparameter not found in functional'); + end + else + hasfun = 0; + fprintf('no functional parameter\n'); + end + + switch(cfg.topoplot) + case 'timelock' + st.nmt.timelock = functional.timelock; + case {'spatialfilter'} + st.nmt.spatialfilter = functional.filter; + case {'leadfield','leadfieldX','leadfieldY','leadfieldZ','leadfieldori'} + st.nmt.grid = functional.grid; + end + + nmt_spm_plot(cfg); + nmt_update_panel(funidx); + nmt_image; +end \ No newline at end of file diff --git a/contrib/nutmegtrip/nmt_spm_plot.m b/contrib/nutmegtrip/nmt_spm_plot.m index 67e705036a..e45bac7dc8 100644 --- a/contrib/nutmegtrip/nmt_spm_plot.m +++ b/contrib/nutmegtrip/nmt_spm_plot.m @@ -1,18 +1,23 @@ -function nmt_spm_plot(cfg) +function nmt_spm_plot(cfg,axsel) % nmt_spm_plot(cfg) % plots desired activation on SPM8 viewer % designed for use via nmt_sourceplot, but possibly usable independently global st -if(~exist('cfg','var')) +if(~exist('cfg','var') || isempty(cfg)) cfg = st.nmt.cfg; end -if(~isfield('cfg','axsel')) - cfg.axsel = 1; +if(~exist('axsel','var')) + axsel = 1; end +if(~isfield(cfg,'colormap')) + cfg.colormap = jet(64); +end + + % if there is no time dimension, set cfg.time_idx to index first and only column if(~isfield(cfg,'time_idx')) cfg.time_idx = [1 1]; @@ -25,22 +30,22 @@ function nmt_spm_plot(cfg) if(cfg.time_idx(1) == cfg.time_idx(2) && cfg.freq_idx(1) == cfg.freq_idx(2)) % single time point selected - fun = st.nmt.fun{cfg.axsel}(:,cfg.time_idx(1),cfg.freq_idx(1)); + fun = st.nmt.fun{axsel}(:,cfg.time_idx(1),cfg.freq_idx(1)); % set colorscale: anatomical (first 64) + functional (second 64) % grayscale for MRI; jet for painted activation - set(st.fig,'Colormap',[gray(64);jet(64)]); + set(st.fig,'Colormap',[gray(64);cfg.colormap]); - scalemax = max(abs(st.nmt.fun{cfg.axsel}(:))); - scalemin = -max(abs(st.nmt.fun{cfg.axsel}(:))); + scalemax = max(abs(st.nmt.fun{axsel}(:))); + scalemin = -max(abs(st.nmt.fun{axsel}(:))); else % time interval selected switch(cfg.plottype) case 'tf' - fun = nmt_ts_intervalpower(st.nmt.fun{cfg.axsel}(:,cfg.time_idx(1):cfg.time_idx(2),cfg.freq_idx(1):cfg.freq_idx(2)),'mean'); + fun = nmt_ts_intervalpower(st.nmt.fun{axsel}(:,cfg.time_idx(1):cfg.time_idx(2),cfg.freq_idx(1):cfg.freq_idx(2)),'mean'); % set colorscale: anatomical (first 64) + functional (second 64) % grayscale for MRI; jet for painted activation - set(st.fig,'Colormap',[gray(64);jet(64)]); + set(st.fig,'Colormap',[gray(64);cfg.colormap]); otherwise - fun = nmt_ts_intervalpower(st.nmt.fun{cfg.axsel}(:,cfg.time_idx(1):cfg.time_idx(2),cfg.freq_idx(1):cfg.freq_idx(2)),'rms'); + fun = nmt_ts_intervalpower(st.nmt.fun{axsel}(:,cfg.time_idx(1):cfg.time_idx(2),cfg.freq_idx(1):cfg.freq_idx(2)),'rms'); % set colorscale: anatomical (first 64) + functional (second 64) % grayscale for MRI; jet for painted activation set(st.fig,'Colormap',[gray(64);hot(64)]); @@ -108,160 +113,6 @@ function nmt_spm_plot(cfg) set(st.nmt.gui.beamin,'String',sprintf('%+g',funval)); +%nmt_update_panel(axsel) -%% update time series, if applicable -if(isfield(st.nmt,'time')) %& ~isfield(st.nmt,'freq')) - set(st.nmt.gui.timeguih,'Visible','On'); % ensure plot is visible - switch(st.nmt.cfg.plottype) - case 'tf' - set(st.nmt.gui.freqguih,'Visible','On'); % ensure plot is visible - if(all(st.nmt.freq(1,:) == [0 0])) % if the first row contains evoked data - nmt_tfplot(st.nmt.gui.ax_ts(cfg.axsel),st.nmt.time,st.nmt.freq(2:end,:),squeeze(st.nmt.fun{cfg.axsel}(st.nmt.cfg.vox_idx,:,2:end)),@nmt_repos_start); - else - nmt_tfplot(st.nmt.gui.ax_ts(cfg.axsel),st.nmt.time,st.nmt.freq,squeeze(st.nmt.fun{cfg.axsel}(st.nmt.cfg.vox_idx,:,:)),@nmt_repos_start); - end - case 'ts' - if(isfinite(st.nmt.cfg.vox_idx)) - plot(st.nmt.gui.ax_ts(cfg.axsel),st.nmt.time,squeeze(st.nmt.fun{cfg.axsel}(st.nmt.cfg.vox_idx,:,:))); - grid(st.nmt.gui.ax_ts(cfg.axsel),'on'); - else - plot(st.nmt.gui.ax_ts(cfg.axsel),st.nmt.time,nan(length(st.nmt.time),1)); - end - - % set y-axis range based on whole volume range (single time point), or - % selected timeslice range - if(st.nmt.cfg.time_idx(1)==st.nmt.cfg.time_idx(2)) - ymin = min(st.nmt.fun{cfg.axsel}(:)); - ymax = max(st.nmt.fun{cfg.axsel}(:)); - else - ymin = min(min(st.nmt.fun{cfg.axsel}(:,st.nmt.cfg.time_idx(1):st.nmt.cfg.time_idx(2)))); - ymax = max(max(st.nmt.fun{cfg.axsel}(:,st.nmt.cfg.time_idx(1):st.nmt.cfg.time_idx(2)))); - end - set(st.nmt.gui.ax_ts(cfg.axsel),'YLim',[ymin ymax]); - - end - set(st.nmt.gui.ax_ts(cfg.axsel),'XLim',st.nmt.time([1 end])); - xlabel(st.nmt.gui.ax_ts(cfg.axsel),['Time (s)']); - - - %% plot vertical line indicating selected time point - switch(st.nmt.cfg.plottype) - case 'ts' - ylim=get(st.nmt.gui.ax_ts(cfg.axsel),'YLim'); - case 'tf' - ylim = [st.nmt.freq(st.nmt.cfg.freq_idx(1),1) st.nmt.freq(st.nmt.cfg.freq_idx(2),2)]; - end - axes(st.nmt.gui.ax_ts(cfg.axsel)); - zlim = get(st.nmt.gui.ax_ts(cfg.axsel),'ZLim'); % selection patch needs to have a Z higher than the TF range so that it's not hidden by the TF plot - h=patch([st.nmt.time(st.nmt.cfg.time_idx(1)) st.nmt.time(st.nmt.cfg.time_idx(2)) st.nmt.time(st.nmt.cfg.time_idx(2)) st.nmt.time(st.nmt.cfg.time_idx(1))]',[ylim(1) ylim(1) ylim(2) ylim(2)]',[zlim(2) zlim(2) zlim(2) zlim(2)],[1 0.4 0.4],'EdgeColor','red'); - - set(st.nmt.gui.ax_ts(cfg.axsel),'ButtonDownFcn',@nmt_repos_start); - - - switch('disabled') % TODO: hook for overlaying time series on TF plot; - % perhaps better solution is independent nmt_spm_plot call for each funparameter - case 'tf' - if(st.nmt.cfg.evokedoverlay) - % trick to overlay time series taken from plotyy.m - ylim=get(st.nmt.gui.ax_ts(cfg.axsel,2),'YLim'); - ts=squeeze(st.nmt.fun{cfg.axsel}(st.nmt.cfg.vox_idx,:,1)); - - axes(st.nmt.gui.ax_ts(cfg.axsel,2)); - plot(st.nmt.gui.ax_ts(cfg.axsel,2),st.nmt.time,ts); - set(st.nmt.gui.ax_ts(cfg.axsel,2),'YAxisLocation','right','Color','none', ... - 'XGrid','off','YGrid','off','Box','off', ... - 'HitTest','off','Visible','on'); - end - end - - %% update GUI textboxes - set(st.nmt.gui.t1,'String',num2str(st.nmt.time(st.nmt.cfg.time_idx(1)))); - set(st.nmt.gui.t2,'String',num2str(st.nmt.time(st.nmt.cfg.time_idx(2)))); - - if(1) - set(st.nmt.gui.f1,'Value',st.nmt.cfg.freq_idx(1)); - set(st.nmt.gui.f2,'Value',st.nmt.cfg.freq_idx(2)); - else - set(st.nmt.gui.f1,'String',num2str(st.nmt.freq(st.nmt.cfg.freq_idx(1),1))); - set(st.nmt.gui.f2,'String',num2str(st.nmt.freq(st.nmt.cfg.freq_idx(2),2))); - end - - %% optionally add topoplot - switch(st.nmt.cfg.topoplot) - case 'timelock' - cfgplot.xlim = st.nmt.time(st.nmt.cfg.time_idx); - cfgplot.zlim = 'maxabs'; - nmt_addtopo(cfgplot,st.nmt.timelock); - case {'spatialfilter','leadfield','leadfieldX','leadfieldY','leadfieldZ','leadfieldori'} - % FIXME: this isn't so elegant :-) - % currently steals some information from timelock structure - topo.label = st.nmt.timelock.label; - topo.grad = st.nmt.timelock.grad; - topo.dimord = 'chan_time'; - topo.time = [0 1 2 3]; % not really time, but selection of magnitude or x/y/z components - switch(cfg.topoplot) - case 'spatialfilter' - topo.time = 0; % fake time (take vector norm) - cfgplot.xlim = [topo.time topo.time]; - cfgplot.zlim = 'maxabs'; - topo.avg = st.nmt.spatialfilter{st.nmt.cfg.vox_idx}'; - case {'leadfield','leadfieldX','leadfieldY','leadfieldZ'} - switch(cfg.topoplot) - case 'leadfield' - topo.time = 0; % fake time (take vector norm) - topo.avg = nut_rownorm(st.nmt.grid.leadfield{st.nmt.cfg.vox_idx}); % TODO: remove dependency on nut_rownorm (from NUTMEG) - case 'leadfieldX' - topo.time = 1; - topo.avg = st.nmt.grid.leadfield{st.nmt.cfg.vox_idx}; - case 'leadfieldY' - topo.time = 2; - topo.avg = st.nmt.grid.leadfield{st.nmt.cfg.vox_idx}; - case 'leadfieldZ' - topo.time = 3; - topo.avg = st.nmt.grid.leadfield{st.nmt.cfg.vox_idx}; - end - cfgplot.xlim = [topo.time topo.time]; - cfgplot.zlim = 'maxabs'; -% case 'leadfieldX' -% topo.time = 1; % fake time (corresponds to x-orientation) -% cfgplot.xlim = [topo.time topo.time]; -% cfgplot.zlim = 'maxabs'; -% topo.avg = st.nmt.grid.leadfield{st.nmt.cfg.vox_idx}; -% case 'leadfieldY' -% topo.time = 2; % fake time (corresponds to y-orientation) -% cfgplot.xlim = [topo.time topo.time]; -% cfgplot.zlim = 'maxabs'; -% topo.avg = st.nmt.grid.leadfield{st.nmt.cfg.vox_idx}; -% case 'leadfieldZ' -% topo.time = 3; % fake time (corresponds to z-orientation) -% cfgplot.xlim = [topo.time topo.time]; -% cfgplot.zlim = 'maxabs'; -% topo.avg = nut_rownorm(st.nmt.grid.leadfield{st.nmt.cfg.vox_idx}); -% case 'leadfieldori' -% error('TODO: not yet implemented'); - end - - nmt_addtopo(cfgplot,topo); - case {'no',''} - % do nothing - otherwise - error('requested cfg.topoplot parameter is not supported.'); - end -end - - - - -function nmt_repos_start(varargin) -global st -set(gcbf,'windowbuttonmotionfcn',@nmt_repos_move, 'windowbuttonupfcn',@nmt_repos_end); -nmt_timeselect('ts'); -%_______________________________________________________________________ -%_______________________________________________________________________ -function nmt_repos_move(varargin) -nmt_timeselect('ts'); -%_______________________________________________________________________ -%_______________________________________________________________________ -function nmt_repos_end(varargin) -set(gcbf,'windowbuttonmotionfcn','', 'windowbuttonupfcn',''); diff --git a/contrib/nutmegtrip/nmt_spmfig_setup.m b/contrib/nutmegtrip/nmt_spmfig_setup.m index 4b6416ade2..aaa0450add 100644 --- a/contrib/nutmegtrip/nmt_spmfig_setup.m +++ b/contrib/nutmegtrip/nmt_spmfig_setup.m @@ -235,24 +235,24 @@ function nmt_spmfig_setup(cfg) if(1) % popup menu for frequency selection st.nmt.gui.f1 = uicontrol('Style','popupmenu','String',num2str(1),'BackgroundColor',nmt_textboxcolor,'Parent',st.fig); % set(st.nmt.gui.f1,'Position',[offx+s*Dims(1)+4*skx+100 offy+s*Dims(2)-100 80 25],... - set(st.nmt.gui.f1,'Position',[offx+s*Dims(1)+4*skx+35 sz(2) 40 25],... + set(st.nmt.gui.f1,'Position',[offx+s*Dims(1)+4*skx+35 sz(2) 50 25],... 'HorizontalAlignment','right','Visible','off','Callback','nmt_timeselect(''textbox'')'); st.nmt.gui.freqguih(2) = st.nmt.gui.f1; st.nmt.gui.freqguih(3) = uicontrol(fg,'Style','Text','String','to','BackgroundColor',[1 1 1],'HorizontalAlignment','left','Parent',st.fig,... - 'Position',[offx+s*Dims(1)+4*skx+80 sz(2)-5 50 25],'Visible','off'); + 'Position',[offx+s*Dims(1)+4*skx+90 sz(2)-5 50 25],'Visible','off'); % 'Position',[offx+s*Dims(1)+4*skx+185 offy+s*Dims(2)-105 50 25],'Visible','off'); st.nmt.gui.f2 = uicontrol('Style','popupmenu','String',num2str(1),'BackgroundColor',nmt_textboxcolor,'Parent',st.fig,... - 'Position',[offx+s*Dims(1)+4*skx+95 sz(2) 40 25],... + 'Position',[offx+s*Dims(1)+4*skx+105 sz(2) 50 25],... 'HorizontalAlignment','right','Visible','off','Callback','nmt_timeselect(''textbox'')'); % 'Position',[offx+s*Dims(1)+4*skx+205 offy+s*Dims(2)-100 80 25],... % 'HorizontalAlignment','right','Visible','off','Callback','nmt_timeselect(''textbox'')'); st.nmt.gui.freqguih(4) = st.nmt.gui.f2; st.nmt.gui.freqguih(5) = uicontrol(fg,'Style','Text','String','Hz','BackgroundColor',[1 1 1],'HorizontalAlignment','left','Parent',st.fig,... - 'Position',[offx+s*Dims(1)+4*skx+140 sz(2)-5 50 25],'Visible','off'); + 'Position',[offx+s*Dims(1)+4*skx+160 sz(2)-5 50 25],'Visible','off'); else % individual text boxes for frequency selection st.nmt.gui.f1 = uicontrol('Style','edit','String',num2str(1),'BackgroundColor',nmt_textboxcolor,'Parent',st.fig); set(st.nmt.gui.f1,'Position',[offx+s*Dims(1)+4*skx+100 offy+s*Dims(2)-100 80 25],... @@ -280,19 +280,19 @@ function nmt_spmfig_setup(cfg) 'Position',[offx+s*Dims(1)+4*skx 20 50 25],... 'Callback','nmt_peaksearch_helper;'); st.nmt.gui.peakdomain = uicontrol(fg,'Style','PopupMenu','String',{'spatiotemporal','spatial','temporal'},'BackgroundColor',nmt_textboxcolor,'HorizontalAlignment','right','Parent',st.fig,... - 'Position',[offx+s*Dims(1)+4*skx+50 20 100 25]); + 'Position',[offx+s*Dims(1)+4*skx+50 20 150 25]); st.nmt.gui.peaktype = uicontrol(fg,'Style','PopupMenu','String',{'mag','max','min'},'BackgroundColor',nmt_textboxcolor,'HorizontalAlignment','right','Parent',st.fig,... - 'Position',[offx+s*Dims(1)+4*skx+150 20 45 25]); + 'Position',[offx+s*Dims(1)+4*skx+195 20 55 25]); st.nmt.gui.searchradius1 = uicontrol('Style','edit','String',num2str(0),'BackgroundColor',nmt_textboxcolor,'Parent',st.fig,... - 'Position',[offx+s*Dims(1)+4*skx+205 20 30 25],... + 'Position',[offx+s*Dims(1)+4*skx+260 20 30 25],... 'HorizontalAlignment','right','Visible','on'); uicontrol(fg,'Style','Text','String','to','BackgroundColor',[1 1 1],'HorizontalAlignment','left','Parent',st.fig,... - 'Position',[offx+s*Dims(1)+4*skx+240 15 30 25]); + 'Position',[offx+s*Dims(1)+4*skx+295 15 30 25]); st.nmt.gui.searchradius2 = uicontrol('Style','edit','String',num2str(200),'BackgroundColor',nmt_textboxcolor,'Parent',st.fig,... - 'Position',[offx+s*Dims(1)+4*skx+255 20 30 25],... + 'Position',[offx+s*Dims(1)+4*skx+310 20 30 25],... 'HorizontalAlignment','right','Visible','on'); uicontrol(fg,'Style','Text','String','mm from cursor','BackgroundColor',[1 1 1],'HorizontalAlignment','left','Parent',st.fig,... - 'Position',[offx+s*Dims(1)+4*skx+295 15 100 25]); + 'Position',[offx+s*Dims(1)+4*skx+350 15 150 25]); diff --git a/contrib/nutmegtrip/nmt_timeselect.m b/contrib/nutmegtrip/nmt_timeselect.m index 32a2ade240..078de0d777 100644 --- a/contrib/nutmegtrip/nmt_timeselect.m +++ b/contrib/nutmegtrip/nmt_timeselect.m @@ -5,8 +5,8 @@ function nmt_timeselect(op) op = ''; end -% TODO: how do we determine which axis was clicked? compare with gca?? -axsel = st.nmt.gui.ax_ts(1); +%axsel = st.nmt.gui.ax_ts(1); +axsel = gca; % for multi panel support switch(op) case 'ts' @@ -59,6 +59,7 @@ function nmt_timeselect(op) st.nmt.cfg.time_idx = st.nmt.cfg.time_idx([2 1]); end - nmt_spm_plot - +for funidx=1:length(st.nmt.fun); + nmt_update_panel(funidx) +end \ No newline at end of file diff --git a/contrib/nutmegtrip/private/nmt_tfplot.m b/contrib/nutmegtrip/private/nmt_tfplot.m index 35ac583bb3..cd2dbe3050 100644 --- a/contrib/nutmegtrip/private/nmt_tfplot.m +++ b/contrib/nutmegtrip/private/nmt_tfplot.m @@ -1,4 +1,4 @@ -function nmt_tfplot(axh,twin,fwin,tf,ButtonDownFcn) +function h_tf = nmt_tfplot(axh,twin,fwin,tf,zlim,ButtonDownFcn) % nutmegtrip uses this helper function to plot time-frequency data % reformat beam.bands and corresponding tf info to include gaps @@ -25,11 +25,11 @@ function nmt_tfplot(axh,twin,fwin,tf,ButtonDownFcn) global st -st.nmt.gui.h_tf = mesh(axh,t,ffin,z); +h_tf = mesh(axh,t,ffin,z); view(axh,2); % '2-D view' of spectrogram -%set(st.nmt.gui.h_tf,'LineStyle','none'); % useful if plot made with 'surf' -set(st.nmt.gui.h_tf,'ButtonDownFcn',ButtonDownFcn); % click on TF plot triggers CallbackFcn -set(st.nmt.gui.h_tf,'LineWidth',2); % seems to prevent faint lines around each TF datapoint +%set(h_tf,'LineStyle','none'); % useful if plot made with 'surf' +set(h_tf,'ButtonDownFcn',ButtonDownFcn); % click on TF plot triggers CallbackFcn +set(h_tf,'LineWidth',2.5); % seems to prevent faint lines around each TF datapoint % limit labels to defined frequencies ytick = unique(f); @@ -46,8 +46,9 @@ function nmt_tfplot(axh,twin,fwin,tf,ButtonDownFcn) end end -caxis(axh,[st.vols{1}.blobs{1}.min st.vols{1}.blobs{1}.max*33/32]); -colormap(axh,[jet(128); 1 1 1]); +%caxis(axh,[st.vols{1}.blobs{1}.min st.vols{1}.blobs{1}.max*17/16]); +caxis(axh,[zlim(1) zlim(2)*17/16]); +colormap(axh,[st.nmt.cfg.colormap; 1 1 1]); set(axh,'Color','none'); % make blank bits transparent % xlabel(axh,'Time'); diff --git a/contrib/nutmegtrip/private/nmt_update_panel.m b/contrib/nutmegtrip/private/nmt_update_panel.m new file mode 100644 index 0000000000..e21173383c --- /dev/null +++ b/contrib/nutmegtrip/private/nmt_update_panel.m @@ -0,0 +1,158 @@ +function nmt_update_panel(axsel) + +global st +%% update time series, if applicable +if(isfield(st.nmt,'time')) %& ~isfield(st.nmt,'freq')) + set(st.nmt.gui.timeguih,'Visible','On'); % ensure plot is visible + switch(st.nmt.cfg.plottype) + case 'tf' + clim = max(abs(st.nmt.fun{axsel}(:))) * [-1 1]; + set(st.nmt.gui.freqguih,'Visible','On'); % ensure plot is visible + if(all(st.nmt.freq(1,:) == [0 0])) % if the first row contains evoked data + st.nmt.gui.h_tf = nmt_tfplot(st.nmt.gui.ax_ts(axsel),st.nmt.time,st.nmt.freq(2:end,:),squeeze(st.nmt.fun{axsel}(st.nmt.cfg.vox_idx,:,2:end)),clim,@nmt_repos_start); + else + st.nmt.gui.h_tf = nmt_tfplot(st.nmt.gui.ax_ts(axsel),st.nmt.time,st.nmt.freq,squeeze(st.nmt.fun{axsel}(st.nmt.cfg.vox_idx,:,:)),clim,@nmt_repos_start); + end + case 'ts' + if(isfinite(st.nmt.cfg.vox_idx)) + plot(st.nmt.gui.ax_ts(axsel),st.nmt.time,squeeze(st.nmt.fun{axsel}(st.nmt.cfg.vox_idx,:,:))); + grid(st.nmt.gui.ax_ts(axsel),'on'); + else + plot(st.nmt.gui.ax_ts(axsel),st.nmt.time,nan(length(st.nmt.time),1)); + end + + % set y-axis range based on whole volume range (single time point), or + % selected timeslice range + if(st.nmt.cfg.time_idx(1)==st.nmt.cfg.time_idx(2)) + ymin = min(st.nmt.fun{axsel}(:)); + ymax = max(st.nmt.fun{axsel}(:)); + else + ymin = min(min(st.nmt.fun{axsel}(:,st.nmt.cfg.time_idx(1):st.nmt.cfg.time_idx(2)))); + ymax = max(max(st.nmt.fun{axsel}(:,st.nmt.cfg.time_idx(1):st.nmt.cfg.time_idx(2)))); + end + set(st.nmt.gui.ax_ts(axsel),'YLim',[ymin ymax]); + + end + set(st.nmt.gui.ax_ts(axsel),'XLim',st.nmt.time([1 end])); + xlabel(st.nmt.gui.ax_ts(axsel),['Time (s)']); + + + %% plot vertical line indicating selected time point + switch(st.nmt.cfg.plottype) + case 'ts' + ylim=get(st.nmt.gui.ax_ts(axsel),'YLim'); + case 'tf' + ylim = [st.nmt.freq(st.nmt.cfg.freq_idx(1),1) st.nmt.freq(st.nmt.cfg.freq_idx(2),2)]; + end + axes(st.nmt.gui.ax_ts(axsel)); + zlim = get(st.nmt.gui.ax_ts(axsel),'ZLim'); % selection patch needs to have a Z higher than the TF range so that it's not hidden by the TF plot + h=patch([st.nmt.time(st.nmt.cfg.time_idx(1)) st.nmt.time(st.nmt.cfg.time_idx(2)) st.nmt.time(st.nmt.cfg.time_idx(2)) st.nmt.time(st.nmt.cfg.time_idx(1))]',[ylim(1) ylim(1) ylim(2) ylim(2)]',[zlim(2) zlim(2) zlim(2) zlim(2)],[1 0.4 0.4],'EdgeColor','red'); + + set(st.nmt.gui.ax_ts(axsel),'ButtonDownFcn',@nmt_repos_start); + + + switch('disabled') % TODO: hook for overlaying time series on TF plot; + % perhaps better solution is independent nmt_spm_plot call for each funparameter + case 'tf' + if(st.nmt.cfg.evokedoverlay) + % trick to overlay time series taken from plotyy.m + ylim=get(st.nmt.gui.ax_ts(axsel,2),'YLim'); + ts=squeeze(st.nmt.fun{axsel}(st.nmt.cfg.vox_idx,:,1)); + + axes(st.nmt.gui.ax_ts(axsel,2)); + plot(st.nmt.gui.ax_ts(axsel,2),st.nmt.time,ts); + set(st.nmt.gui.ax_ts(axsel,2),'YAxisLocation','right','Color','none', ... + 'XGrid','off','YGrid','off','Box','off', ... + 'HitTest','off','Visible','on'); + end + end + + %% update GUI textboxes + set(st.nmt.gui.t1,'String',num2str(st.nmt.time(st.nmt.cfg.time_idx(1)))); + set(st.nmt.gui.t2,'String',num2str(st.nmt.time(st.nmt.cfg.time_idx(2)))); + + if(1) + set(st.nmt.gui.f1,'Value',st.nmt.cfg.freq_idx(1)); + set(st.nmt.gui.f2,'Value',st.nmt.cfg.freq_idx(2)); + else + set(st.nmt.gui.f1,'String',num2str(st.nmt.freq(st.nmt.cfg.freq_idx(1),1))); + set(st.nmt.gui.f2,'String',num2str(st.nmt.freq(st.nmt.cfg.freq_idx(2),2))); + end + + %% optionally add topoplot + switch(st.nmt.cfg.topoplot) + case 'timelock' + cfgplot.xlim = st.nmt.time(st.nmt.cfg.time_idx); + cfgplot.zlim = 'maxabs'; + nmt_addtopo(cfgplot,st.nmt.timelock); + case {'spatialfilter','leadfield','leadfieldX','leadfieldY','leadfieldZ','leadfieldori'} + % FIXME: this isn't so elegant :-) + % currently steals some information from timelock structure + topo.label = st.nmt.timelock.label; + topo.grad = st.nmt.timelock.grad; + topo.dimord = 'chan_time'; + topo.time = [0 1 2 3]; % not really time, but selection of magnitude or x/y/z components + switch(cfg.topoplot) + case 'spatialfilter' + topo.time = 0; % fake time (take vector norm) + cfgplot.xlim = [topo.time topo.time]; + cfgplot.zlim = 'maxabs'; + topo.avg = st.nmt.spatialfilter{st.nmt.cfg.vox_idx}'; + case {'leadfield','leadfieldX','leadfieldY','leadfieldZ'} + switch(cfg.topoplot) + case 'leadfield' + topo.time = 0; % fake time (take vector norm) + topo.avg = nut_rownorm(st.nmt.grid.leadfield{st.nmt.cfg.vox_idx}); % TODO: remove dependency on nut_rownorm (from NUTMEG) + case 'leadfieldX' + topo.time = 1; + topo.avg = st.nmt.grid.leadfield{st.nmt.cfg.vox_idx}; + case 'leadfieldY' + topo.time = 2; + topo.avg = st.nmt.grid.leadfield{st.nmt.cfg.vox_idx}; + case 'leadfieldZ' + topo.time = 3; + topo.avg = st.nmt.grid.leadfield{st.nmt.cfg.vox_idx}; + end + cfgplot.xlim = [topo.time topo.time]; + cfgplot.zlim = 'maxabs'; +% case 'leadfieldX' +% topo.time = 1; % fake time (corresponds to x-orientation) +% cfgplot.xlim = [topo.time topo.time]; +% cfgplot.zlim = 'maxabs'; +% topo.avg = st.nmt.grid.leadfield{st.nmt.cfg.vox_idx}; +% case 'leadfieldY' +% topo.time = 2; % fake time (corresponds to y-orientation) +% cfgplot.xlim = [topo.time topo.time]; +% cfgplot.zlim = 'maxabs'; +% topo.avg = st.nmt.grid.leadfield{st.nmt.cfg.vox_idx}; +% case 'leadfieldZ' +% topo.time = 3; % fake time (corresponds to z-orientation) +% cfgplot.xlim = [topo.time topo.time]; +% cfgplot.zlim = 'maxabs'; +% topo.avg = nut_rownorm(st.nmt.grid.leadfield{st.nmt.cfg.vox_idx}); +% case 'leadfieldori' +% error('TODO: not yet implemented'); + end + + nmt_addtopo(cfgplot,topo); + case {'no',''} + % do nothing + otherwise + error('requested cfg.topoplot parameter is not supported.'); + end +end + + +function nmt_repos_start(varargin) +global st +set(gcbf,'windowbuttonmotionfcn',@nmt_repos_move, 'windowbuttonupfcn',@nmt_repos_end); +nmt_timeselect('ts'); +%_______________________________________________________________________ +%_______________________________________________________________________ +function nmt_repos_move(varargin) +nmt_timeselect('ts'); +%_______________________________________________________________________ +%_______________________________________________________________________ +function nmt_repos_end(varargin) +set(gcbf,'windowbuttonmotionfcn','', 'windowbuttonupfcn',''); + From 3df2aaaabaf33ee62ffd47e5df12eceec4d6cb98 Mon Sep 17 00:00:00 2001 From: Sarang Dalal Date: Thu, 15 Sep 2016 19:34:28 +0200 Subject: [PATCH 07/11] nutmegtrip: customized getdimord --- contrib/nutmegtrip/private/getdimord.m | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/contrib/nutmegtrip/private/getdimord.m b/contrib/nutmegtrip/private/getdimord.m index 1249f954c2..e87e1f11fa 100644 --- a/contrib/nutmegtrip/private/getdimord.m +++ b/contrib/nutmegtrip/private/getdimord.m @@ -341,7 +341,7 @@ dimord = 'rpt_pos_freq'; end - case {'mom'} + case {'mom','itc','aa','stat','pval','statitc','pitc'} if isequal(datsiz, [npos nori nrpt]) dimord = 'pos_ori_rpt'; elseif isequal(datsiz, [npos nori ntime]) @@ -378,6 +378,8 @@ dimord = 'pos_rpt_ori_time'; elseif isequalwithoutnans(datsiz, [npos nrpt 1 ntime]) dimord = 'pos_rpt_ori_time'; + elseif isequal(datsiz, [npos nfreq ntime]) + dimord = 'pos_freq_time'; end case {'filter'} From 515f9dd2fc0faad29328505fb6a2827e739cbd2f Mon Sep 17 00:00:00 2001 From: Robert Oostenveld Date: Thu, 15 Sep 2016 19:38:22 +0200 Subject: [PATCH 08/11] automatically synchronized identical files to 88181f75ff3bba9042a06c72249effa2c36d96a3 --- fileio/private/getdimord.m | 4 +++- private/getdimord.m | 4 +++- test/private/getdimord.m | 4 +++- utilities/private/getdimord.m | 4 +++- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/fileio/private/getdimord.m b/fileio/private/getdimord.m index 1249f954c2..e87e1f11fa 100644 --- a/fileio/private/getdimord.m +++ b/fileio/private/getdimord.m @@ -341,7 +341,7 @@ dimord = 'rpt_pos_freq'; end - case {'mom'} + case {'mom','itc','aa','stat','pval','statitc','pitc'} if isequal(datsiz, [npos nori nrpt]) dimord = 'pos_ori_rpt'; elseif isequal(datsiz, [npos nori ntime]) @@ -378,6 +378,8 @@ dimord = 'pos_rpt_ori_time'; elseif isequalwithoutnans(datsiz, [npos nrpt 1 ntime]) dimord = 'pos_rpt_ori_time'; + elseif isequal(datsiz, [npos nfreq ntime]) + dimord = 'pos_freq_time'; end case {'filter'} diff --git a/private/getdimord.m b/private/getdimord.m index 1249f954c2..e87e1f11fa 100644 --- a/private/getdimord.m +++ b/private/getdimord.m @@ -341,7 +341,7 @@ dimord = 'rpt_pos_freq'; end - case {'mom'} + case {'mom','itc','aa','stat','pval','statitc','pitc'} if isequal(datsiz, [npos nori nrpt]) dimord = 'pos_ori_rpt'; elseif isequal(datsiz, [npos nori ntime]) @@ -378,6 +378,8 @@ dimord = 'pos_rpt_ori_time'; elseif isequalwithoutnans(datsiz, [npos nrpt 1 ntime]) dimord = 'pos_rpt_ori_time'; + elseif isequal(datsiz, [npos nfreq ntime]) + dimord = 'pos_freq_time'; end case {'filter'} diff --git a/test/private/getdimord.m b/test/private/getdimord.m index 1249f954c2..e87e1f11fa 100644 --- a/test/private/getdimord.m +++ b/test/private/getdimord.m @@ -341,7 +341,7 @@ dimord = 'rpt_pos_freq'; end - case {'mom'} + case {'mom','itc','aa','stat','pval','statitc','pitc'} if isequal(datsiz, [npos nori nrpt]) dimord = 'pos_ori_rpt'; elseif isequal(datsiz, [npos nori ntime]) @@ -378,6 +378,8 @@ dimord = 'pos_rpt_ori_time'; elseif isequalwithoutnans(datsiz, [npos nrpt 1 ntime]) dimord = 'pos_rpt_ori_time'; + elseif isequal(datsiz, [npos nfreq ntime]) + dimord = 'pos_freq_time'; end case {'filter'} diff --git a/utilities/private/getdimord.m b/utilities/private/getdimord.m index 1249f954c2..e87e1f11fa 100644 --- a/utilities/private/getdimord.m +++ b/utilities/private/getdimord.m @@ -341,7 +341,7 @@ dimord = 'rpt_pos_freq'; end - case {'mom'} + case {'mom','itc','aa','stat','pval','statitc','pitc'} if isequal(datsiz, [npos nori nrpt]) dimord = 'pos_ori_rpt'; elseif isequal(datsiz, [npos nori ntime]) @@ -378,6 +378,8 @@ dimord = 'pos_rpt_ori_time'; elseif isequalwithoutnans(datsiz, [npos nrpt 1 ntime]) dimord = 'pos_rpt_ori_time'; + elseif isequal(datsiz, [npos nfreq ntime]) + dimord = 'pos_freq_time'; end case {'filter'} From c7b0d40ae2a764cebf8fca51c562e45cf21730e0 Mon Sep 17 00:00:00 2001 From: vlitvak Date: Mon, 19 Sep 2016 16:39:41 +0100 Subject: [PATCH 09/11] enhancement - added neuromag306_combined to the list of meg sensor types --- forward/ft_senstype.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forward/ft_senstype.m b/forward/ft_senstype.m index 6342b8a595..f7f60ade93 100644 --- a/forward/ft_senstype.m +++ b/forward/ft_senstype.m @@ -437,7 +437,7 @@ case 'egi' type = any(strcmp(type, {'egi32' 'egi64' 'egi128' 'egi256'})); case 'meg' - type = any(strcmp(type, {'meg' 'magnetometer' 'ctf' 'bti' 'ctf64' 'ctf151' 'ctf275' 'ctf151_planar' 'ctf275_planar' 'neuromag122' 'neuromag306' 'bti148' 'bti148_planar' 'bti248' 'bti248_planar' 'bti248grad' 'bti248grad_planar' 'yokogawa9' 'yokogawa160' 'yokogawa160_planar' 'yokogawa64' 'yokogawa64_planar' 'yokogawa440' 'itab' 'itab28' 'itab153' 'itab153_planar'})); + type = any(strcmp(type, {'meg' 'magnetometer' 'ctf' 'bti' 'ctf64' 'ctf151' 'ctf275' 'ctf151_planar' 'ctf275_planar' 'neuromag122' 'neuromag306' 'neuromag306_combined' 'bti148' 'bti148_planar' 'bti248' 'bti248_planar' 'bti248grad' 'bti248grad_planar' 'yokogawa9' 'yokogawa160' 'yokogawa160_planar' 'yokogawa64' 'yokogawa64_planar' 'yokogawa440' 'itab' 'itab28' 'itab153' 'itab153_planar'})); case 'ctf' type = any(strcmp(type, {'ctf' 'ctf64' 'ctf151' 'ctf275' 'ctf151_planar' 'ctf275_planar'})); case 'bti' From 63448b1ab3a7edc59714a6f6263f06c22a0c1290 Mon Sep 17 00:00:00 2001 From: vlitvak Date: Mon, 19 Sep 2016 16:45:38 +0100 Subject: [PATCH 10/11] enhancement - increased the size of points for electrode plotting and added color option --- plotting/ft_plot_sens.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plotting/ft_plot_sens.m b/plotting/ft_plot_sens.m index 82f142ef57..23f09f7289 100644 --- a/plotting/ft_plot_sens.m +++ b/plotting/ft_plot_sens.m @@ -256,7 +256,7 @@ switch coilshape case 'point' - hs = plot3(pos(:,1), pos(:,2), pos(:,3), style); + hs = plot3(pos(:,1), pos(:,2), pos(:,3), style, 'MarkerSize', 30, 'Color', edgecolor); case 'circle' plotcoil(pos, ori, [], coilsize, coilshape, 'edgecolor', edgecolor, 'facecolor', facecolor, 'edgealpha', edgealpha, 'facealpha', facealpha); case 'square' From be73163a923b8340a37ddf7754600b108a3363b2 Mon Sep 17 00:00:00 2001 From: Robert Oostenveld Date: Tue, 20 Sep 2016 09:14:49 +0200 Subject: [PATCH 11/11] automatically synchronized identical files to aa143c1262c8f10562e9e10bfe6d93c26a2719f1 --- fileio/private/ft_senstype.m | 2 +- inverse/private/ft_senstype.m | 2 +- plotting/private/ft_senstype.m | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/fileio/private/ft_senstype.m b/fileio/private/ft_senstype.m index 6342b8a595..f7f60ade93 100644 --- a/fileio/private/ft_senstype.m +++ b/fileio/private/ft_senstype.m @@ -437,7 +437,7 @@ case 'egi' type = any(strcmp(type, {'egi32' 'egi64' 'egi128' 'egi256'})); case 'meg' - type = any(strcmp(type, {'meg' 'magnetometer' 'ctf' 'bti' 'ctf64' 'ctf151' 'ctf275' 'ctf151_planar' 'ctf275_planar' 'neuromag122' 'neuromag306' 'bti148' 'bti148_planar' 'bti248' 'bti248_planar' 'bti248grad' 'bti248grad_planar' 'yokogawa9' 'yokogawa160' 'yokogawa160_planar' 'yokogawa64' 'yokogawa64_planar' 'yokogawa440' 'itab' 'itab28' 'itab153' 'itab153_planar'})); + type = any(strcmp(type, {'meg' 'magnetometer' 'ctf' 'bti' 'ctf64' 'ctf151' 'ctf275' 'ctf151_planar' 'ctf275_planar' 'neuromag122' 'neuromag306' 'neuromag306_combined' 'bti148' 'bti148_planar' 'bti248' 'bti248_planar' 'bti248grad' 'bti248grad_planar' 'yokogawa9' 'yokogawa160' 'yokogawa160_planar' 'yokogawa64' 'yokogawa64_planar' 'yokogawa440' 'itab' 'itab28' 'itab153' 'itab153_planar'})); case 'ctf' type = any(strcmp(type, {'ctf' 'ctf64' 'ctf151' 'ctf275' 'ctf151_planar' 'ctf275_planar'})); case 'bti' diff --git a/inverse/private/ft_senstype.m b/inverse/private/ft_senstype.m index 6342b8a595..f7f60ade93 100644 --- a/inverse/private/ft_senstype.m +++ b/inverse/private/ft_senstype.m @@ -437,7 +437,7 @@ case 'egi' type = any(strcmp(type, {'egi32' 'egi64' 'egi128' 'egi256'})); case 'meg' - type = any(strcmp(type, {'meg' 'magnetometer' 'ctf' 'bti' 'ctf64' 'ctf151' 'ctf275' 'ctf151_planar' 'ctf275_planar' 'neuromag122' 'neuromag306' 'bti148' 'bti148_planar' 'bti248' 'bti248_planar' 'bti248grad' 'bti248grad_planar' 'yokogawa9' 'yokogawa160' 'yokogawa160_planar' 'yokogawa64' 'yokogawa64_planar' 'yokogawa440' 'itab' 'itab28' 'itab153' 'itab153_planar'})); + type = any(strcmp(type, {'meg' 'magnetometer' 'ctf' 'bti' 'ctf64' 'ctf151' 'ctf275' 'ctf151_planar' 'ctf275_planar' 'neuromag122' 'neuromag306' 'neuromag306_combined' 'bti148' 'bti148_planar' 'bti248' 'bti248_planar' 'bti248grad' 'bti248grad_planar' 'yokogawa9' 'yokogawa160' 'yokogawa160_planar' 'yokogawa64' 'yokogawa64_planar' 'yokogawa440' 'itab' 'itab28' 'itab153' 'itab153_planar'})); case 'ctf' type = any(strcmp(type, {'ctf' 'ctf64' 'ctf151' 'ctf275' 'ctf151_planar' 'ctf275_planar'})); case 'bti' diff --git a/plotting/private/ft_senstype.m b/plotting/private/ft_senstype.m index 6342b8a595..f7f60ade93 100644 --- a/plotting/private/ft_senstype.m +++ b/plotting/private/ft_senstype.m @@ -437,7 +437,7 @@ case 'egi' type = any(strcmp(type, {'egi32' 'egi64' 'egi128' 'egi256'})); case 'meg' - type = any(strcmp(type, {'meg' 'magnetometer' 'ctf' 'bti' 'ctf64' 'ctf151' 'ctf275' 'ctf151_planar' 'ctf275_planar' 'neuromag122' 'neuromag306' 'bti148' 'bti148_planar' 'bti248' 'bti248_planar' 'bti248grad' 'bti248grad_planar' 'yokogawa9' 'yokogawa160' 'yokogawa160_planar' 'yokogawa64' 'yokogawa64_planar' 'yokogawa440' 'itab' 'itab28' 'itab153' 'itab153_planar'})); + type = any(strcmp(type, {'meg' 'magnetometer' 'ctf' 'bti' 'ctf64' 'ctf151' 'ctf275' 'ctf151_planar' 'ctf275_planar' 'neuromag122' 'neuromag306' 'neuromag306_combined' 'bti148' 'bti148_planar' 'bti248' 'bti248_planar' 'bti248grad' 'bti248grad_planar' 'yokogawa9' 'yokogawa160' 'yokogawa160_planar' 'yokogawa64' 'yokogawa64_planar' 'yokogawa440' 'itab' 'itab28' 'itab153' 'itab153_planar'})); case 'ctf' type = any(strcmp(type, {'ctf' 'ctf64' 'ctf151' 'ctf275' 'ctf151_planar' 'ctf275_planar'})); case 'bti'