From d6db9d4c0393c1a9cd3689afc872749f13b0598e Mon Sep 17 00:00:00 2001 From: Sebastian Bosse Date: Wed, 8 Aug 2018 23:39:35 +0200 Subject: [PATCH 01/62] changed dimensionality index of trials --- +mrC/+Simulate/CreateAxx.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/+mrC/+Simulate/CreateAxx.m b/+mrC/+Simulate/CreateAxx.m index 5c39d56..ed52526 100644 --- a/+mrC/+Simulate/CreateAxx.m +++ b/+mrC/+Simulate/CreateAxx.m @@ -9,6 +9,7 @@ %========================================================== % Author: Elham Barzegaran, 3/26/2018 +% modified by sebastian bosse 8/4/2018 %% EEGAxx = mrC.axx(); @@ -47,7 +48,7 @@ EEGAxx.dTms = 1000/opt.signalsf; % calculate spectrum -EEGAxx.nTrl = size(EEGAxx.Wave,4); +EEGAxx.nTrl = size(EEGAxx.Wave,3); NumEp = floor(size(EEGData,1)/WLF); EEGSPEC = fft(squeeze(permute(mean(reshape(EEGData(1:NumEp*WLF,:),[WLF NumEp size(EEGData,2) size(EEGData,3)]),2),[1 3 2 4])),WLF,1); f = opt.signalsf*(0:(WLF/2))/WLF; % frequencies From 40b88b770e0c3477f54d6c1638ee597456c0bc52 Mon Sep 17 00:00:00 2001 From: Sebastian Bosse Date: Wed, 8 Aug 2018 23:42:10 +0200 Subject: [PATCH 02/62] added noise generation for mulitple trials --- +mrC/+Simulate/SimulateProject.m | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/+mrC/+Simulate/SimulateProject.m b/+mrC/+Simulate/SimulateProject.m index 47ee4ab..f086d88 100644 --- a/+mrC/+Simulate/SimulateProject.m +++ b/+mrC/+Simulate/SimulateProject.m @@ -30,6 +30,7 @@ % % signalFF: a seedNum x 1 vector: determines the fundamental % frequencis of sources + % nTrials: Number of trials. Noise is redrawn for each trial. % (ROI Parameters) % rois a cell array of roi structure that can be @@ -122,6 +123,7 @@ %-------------------------------------------------------------------------- % The function was originally written by Peter Kohler, ... % Latest modification: Elham Barzegaran, 03.26.2018 + % Modifications: Sebastian Bosse 8/2/2018 % NOTE: This function is a part of mrC toolboxs %% =====================Prepare input variables============================ @@ -144,7 +146,8 @@ 'anatomyPath' , [],... 'plotting' , 0 ,... 'Save' ,true,... - 'cndNum' ,1 ... + 'cndNum' ,1, ... + 'nTrials' ,1 ... ); % Roi Type, the names should be according to folders in (svdnl/anatomy/...) @@ -319,8 +322,11 @@ % ----- Generate noise----- % this noise is NS x srcNum matrix, where srcNum is the number of source points on the cortical meshe - [noiseSignal, pink_noise,~, alpha_noise] = mrC.Simulate.GenerateNoise(opt.signalsf, NS, size(spat_dists,1), Noise.mu, AlphaSrc, noise_mixing_data,Noise.spatial_normalization_type); - + noise_signal = zeros(NS, size(spat_dists,1), opt.nTrials); + for trial_id =1:opt.nTrials % this could be solved more elegantly in GenerateNoise as well.. + [thisNoiseSignal, pink_noise,~, alpha_noise] = mrC.Simulate.GenerateNoise(opt.signalsf, NS, size(spat_dists,1), Noise.mu, AlphaSrc, noise_mixing_data,Noise.spatial_normalization_type); + noiseSignal(:,:,trial_id) = thisNoiseSignal ; + end %visualizeNoise(noiseSignal, spat_dists, surfData,opt.signalsf) % Just to visualize noise on the cortical surface %visualizeNoise(alpha_noise, spat_dists, surfData,opt.signalsf) % From 987d0aaff975903288413583cc8304faf7ca5d68 Mon Sep 17 00:00:00 2001 From: Sebastian Bosse Date: Wed, 8 Aug 2018 23:46:16 +0200 Subject: [PATCH 03/62] considering more than one trial --- +mrC/+Simulate/SrcSigMtx.m | 36 +++++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/+mrC/+Simulate/SrcSigMtx.m b/+mrC/+Simulate/SrcSigMtx.m index 9767cc8..815dd18 100644 --- a/+mrC/+Simulate/SrcSigMtx.m +++ b/+mrC/+Simulate/SrcSigMtx.m @@ -17,7 +17,7 @@ % % signalArray: ns x n matrix: Containing the signals for the ROIs % - % noise: ns x nsrc matrix: Containing the noises signal in source space + % noise: ns x nsrc x ntrls matrix: Containing the noises signal in source space % % lambda: Parameter for defining of SNR: How to add noise and signal % @@ -34,6 +34,7 @@ % Elham Barzegaran 2.27.2018 % Updated EB, 6.5.2018 + % Modified SB, 8/2/2018 %% if ~exist('RoiSize','var'), RoiSize = [];end @@ -70,33 +71,47 @@ spatfunc = RoiSpatFunc(roiChunk,surfData,RoiSize,[],funcType,plotRoi); % place seed signal array in source space - sourceTemp = zeros(size(noise)); + + sourceTemp = zeros(size(noise,1),size(noise,2)); for s = 1: size(signalArray,2)% place the signal for each seed sourceTemp = sourceTemp + repmat (signalArray(:,s),[1 size(sourceTemp,2)]).*(repmat(spatfunc(:,s),[1 size(sourceTemp,1)])'); end % Normalize the source signal if strcmp(spatial_normalization_type,'active_nodes') - n_active_nodes_signal = sum(sum(abs(sourceTemp))~=0) ; + n_active_nodes_signal = squeeze(sum(sum(abs(sourceTemp))~=0) ) ; sourceTemp = n_active_nodes_signal * sourceTemp/norm(sourceTemp,'fro') ; elseif strcmp(spatial_normalization_type,'all_nodes') sourceTemp = sourceTemp/norm(sourceTemp,'fro') ; else error('%s is not implemented as spatial normalization method', spatial_normalization_type) end - - else sourceTemp = zeros(size(noise)); end +if ndims(noise) == 3 + sourceTemp = repmat(sourceTemp,[1,1,size(noise,3)]); +end + % Adds noise to source signal pow = 1; sourceData = ((lambda/(lambda+1))^pow)*sourceTemp + ((1/(lambda+1))^pow) *noise; -sourceData = sourceData/norm(sourceData,'fro') ;% signal and noise are correlated randomly (not on average!). dirty hack: normalize sum -% Generate EEG data by multiplication to forward matrix -EEGData = sourceData*fwdMatrix'; +if ndims(sourceData) == 3 + EEGData = zeros(size(sourceData,1),size(fwdMatrix,1),size(sourceData,3)) ; + for trial_idx = 1:size(sourceData,3) + sourceData(:,:,trial_idx) = sourceData(:,:,trial_idx)/norm(sourceData(:,:,trial_idx),'fro') ;% signal and noise are correlated randomly (not on average!). dirty workaround: normalize sum + % Generate EEG data by multiplication to forward matrix + EEGData(:,:,trial_idx) = sourceData(:,:,trial_idx)*fwdMatrix'; + + end +else + sourceData = sourceData/norm(sourceData,'fro') ;% signal and noise are correlated randomly (not on average!). dirty hack: normalize sum + % Generate EEG data by multiplication to forward matrix + EEGData = sourceData*fwdMatrix'; + +end % there should be another step: add measurement noise to EEG? end @@ -131,7 +146,10 @@ for i = 1:numel(RoiIdx) vertIdx_orig = find(roiChunk(:,RoiIdx(i))); [RoiV, RoiF,vertIdx] = SurfSubsample(vertices, faces,vertIdx_orig,'union'); - [RoiVertices{i}, RoiFaces{i},vertIdxR,~,RoiDist,radius] = ResizeRoi(RoiV,RoiF,vertIdx,RoiSize,'geodesic'); + %[RoiVertices{i}, RoiFaces{i},vertIdxR,~,RoiDist,radius] = ResizeRoi(RoiV,RoiF,vertIdx,RoiSize,'geodesic'); + % TODO: just a quick hack by sb, since surfing (and thus geodesic distance + % type) is not available on my machine + [RoiVertices{i}, RoiFaces{i},vertIdxR,~,RoiDist,radius] = ResizeRoi(RoiV,RoiF,vertIdx,RoiSize,'euclidean'); if (~isempty(vertIdx)) && (~isempty(vertIdxR)) switch funcType case 'uniform' From a1e28a37daf4073a29fa7a4cd71bba9605c408a2 Mon Sep 17 00:00:00 2001 From: Sebastian Bosse Date: Wed, 8 Aug 2018 23:46:48 +0200 Subject: [PATCH 04/62] added visualization in component space --- +mrC/+Simulate/PlotEEG.m | 63 +++++++++++++++++++++++++++++++--------- 1 file changed, 50 insertions(+), 13 deletions(-) diff --git a/+mrC/+Simulate/PlotEEG.m b/+mrC/+Simulate/PlotEEG.m index 1227508..dc31d5a 100644 --- a/+mrC/+Simulate/PlotEEG.m +++ b/+mrC/+Simulate/PlotEEG.m @@ -1,4 +1,4 @@ -function h = PlotEEG(ASDEEG,Freq,savepath,subID, masterList,signalFF,SignalType,jcolors,Mode,EOI,FOI) +function h = PlotEEG(ASDEEG,Freq,savepath,subID, masterList,signalFF,SignalType,jcolors,Mode,EOI,FOI,A) % This function provides a dual plot of electrode amplitude/phase spectrum and topographic % map of amplitude/phase at a specific frequency (similar to powerDiva) @@ -26,9 +26,10 @@ % Written by ELham Barzegaran,3.7.2018 % Latest modification: 6.6.2018 +% modified by sebastian bosse 8.8.2018 %% set parameters -if ~exist('SignalType','var')% plot phase or amp +if ~exist('SignalType','var')|| isempty(SignalType) % plot phase or amp if min(ASDEEG(:))>0 SignalType = 'Amplitude'; else @@ -60,6 +61,16 @@ if ~exist('Mode','var') || isempty(Mode) Mode = 'Interact'; end + +% allow for visualization of spatial filters +% what do we need? +if ~exist('A','var') || isempty(Mode) + space = 'chann'; +else + space = 'comp'; + EOI = 1 ; % start with the first component +end + %% Plot prepration Probs{1} = {'facecolor','none','edgecolor','none','markersize',10,'marker','o','markerfacecolor','g' ,'MarkerEdgeColor','k','LineWidth',.5};% plotting parameters if ~exist('jcolors','var') || isempty(jcolors) @@ -81,6 +92,7 @@ subplot(2,2,2),axis off;% Show simulated signal information + nrs = max(numel(masterList),3); text(.1,1,['' subID],'fontsize',FS+1,'fontweight','bold'); for i= 1:numel(masterList) @@ -98,19 +110,27 @@ I = 1; while(I) if strcmp(SignalType,'Amplitude') - if N == 1, colorbarLimits = [-0 max(ASDEEG(FOI,:))]; - else colorbarLimits = [-0 max(ASDEEG(:))]; + if strcmpi(space,'comp') + colorbarLimits = [-0 max(A(:,EOI)*ASDEEG(FOI,EOI)')]; + else + if N == 1, colorbarLimits = [-0 max(ASDEEG(FOI,:))]; + else colorbarLimits = [-0 max(ASDEEG(:))]; + end end elseif strcmp(SignalType,'Phase') colorbarLimits = [min(ASDEEG(:)) max(ASDEEG(:))]; end if exist('sp1','var'),delete(sp1);end % topography map plot - sp1 = subplot(1,2,1); - if strcmpi(Mode,'Interact'), + sp1 = subplot(1,2,1); + if strcmpi(Mode,'Interact') && ~strcmpi(space,'comp'), mrC.plotOnEgi(ASDEEG(FOI,:)',colorbarLimits,false,EOI,false,Probs); else - mrC.plotOnEgi(ASDEEG(FOI,:)',colorbarLimits,false,[],false,Probs); + if strcmpi(space,'comp') + mrC.plotOnEgi(A(:,EOI)*ASDEEG(FOI,EOI)',colorbarLimits,false,[],false,Probs); + else + mrC.plotOnEgi(ASDEEG(FOI,:)',colorbarLimits,false,[],false,Probs); + end end title(['Frequency = ' num2str(Freq(FOI)) 'Hz'],'fontsize',FS); set(sp1,'tag',num2str(1)); @@ -118,7 +138,7 @@ colorbar; if exist('sp2','var'),delete(sp2);end % spectrum plot - sp2 = subplot(2,2,4); + sp2 = subplot(2,2,4); bar(Freq(1:Fmax), ASDEEG(1:Fmax,EOI),.15); xlim([Freq(1) Freq(Fmax)]); xlabel('Frequency(Hz)','fontsize',FS-2); @@ -132,8 +152,19 @@ set(sp2,'tag',num2str(2)); hold on; bar(Freq(FOI), ASDEEG(FOI,EOI),.4,'FaceColor','g','EdgeColor','g'); - title(['Electrode ' num2str(num2str(EOI))],'fontsize',FS); + % draw buttons for channel selection + if strcmpi(space,'comp') + popup = uicontrol('Style', 'popup',... + 'String', string(1:size(A,1)),... + 'Position', [20 340 100 50],... + 'Callback',@set_component); + end + if strcmpi(space,'comp') + title(['Component ' num2str(num2str(EOI))],'fontsize',FS); + else + title(['Electrode ' num2str(num2str(EOI))],'fontsize',FS); + end %% Reads keyboard or mouse click if strcmpi(Mode,'Interact') w = waitforbuttonpress; @@ -159,10 +190,11 @@ % update location switch SPI case '1' - Epos2= repmat([x y],[128 1]); - dis = sqrt(sum((tEpos-Epos2).^2,2)); - [~,EOI] = min(dis); - + if ~strcmpi(space,'comp') + Epos2= repmat([x y],[128 1]); + dis = sqrt(sum((tEpos-Epos2).^2,2)); + [~,EOI] = min(dis); + end case '2' [~,FOI] = min(abs(repmat(x,[1 size(ASDEEG,1)])-Freq)); end @@ -172,5 +204,10 @@ end end + function set_component(source,event) + EOI = source.Value ; + end + + %print(fullfile(savepath,['SimEEG_Subject' subID 'Electrode' num2str(EOI) '_Freq' num2str(Freq(FOI)) 'Hz_' SignalType '.tif']),'-dtiff','-r300');% Later I can update this to contain the simulation parameters end \ No newline at end of file From 4e3ed2aeb44f18dd0282d824da3aa32341b97e78 Mon Sep 17 00:00:00 2001 From: Sebastian Bosse Date: Fri, 10 Aug 2018 11:50:53 +0200 Subject: [PATCH 05/62] added pca --- +mrC/+SpatialFilters/PCA.m | 58 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 +mrC/+SpatialFilters/PCA.m diff --git a/+mrC/+SpatialFilters/PCA.m b/+mrC/+SpatialFilters/PCA.m new file mode 100644 index 0000000..ad369b3 --- /dev/null +++ b/+mrC/+SpatialFilters/PCA.m @@ -0,0 +1,58 @@ +function [OutAxx,W,A] = PCA(InAxx,freqs) + +% This function calculates finds a spatial filter based on the the PCA of +% EEGAxx. + +% INPUT: + % InAxx: EEG data in Axx format + % freqs: Frequnecies considered +% OUTPUT: + % OutAxx: Data in component space in Axx format + % W: Spatial filter + % A: Activation pattern + +% Written by Sebastian Bosse, 3.8.2018 + +if exist('freqs','var') && ~isempty(freqs) + freq_range = 0:InAxx.dFHz:InAxx.dFHz*(InAxx.nFr-1); + freq_idxs = find(ismember(freq_range,freqs)); + + real_part = InAxx.Cos(freq_idxs,:,:); + imag_part = InAxx.Sin(freq_idxs,:,:); + + if ismember(0,freqs) + cmplx_signal = cat(1, real_part,real_part) ... % even real part + + cat(1, -imag_part,imag_part); % odd imag part + else + cmplx_signal = cat(1, real_part,InAxx.Cos(1,:,:),real_part) ... % even real part + + cat(1, -imag_part,InAxx.Sin(1,:,:),imag_part); % odd imag part + end +else % just take it all + cmplx_signal = cat(1, InAxx.Cos(2:end,:,:),InAxx.Cos) ... % even real part + + cat(1, -InAxx.Sin(2:end,:,:),InAxx.Sin); % odd imag part +end +% C = zeros(size(cmplx_signal,2)) ; +% for trial_idx = 1:size(cmplx_signal,3) +% C = C+cmplx_signal(:,:,trial_idx)'*conj(cmplx_signal(:,:,trial_idx)) ; +% end +% more efficient by avoiding the loop +C =(reshape(permute(cmplx_signal,[2,1,3]),size(cmplx_signal,2),[]))*conj(reshape(permute(cmplx_signal,[2,1,3]),size(cmplx_signal,2),[])'); + + +[W,D] = eig(C); +[D,sorted_idx] = sort(diag(D),'descend') ; +W = W(:,sorted_idx); +A = C * W / (W'*C*W); + + +% project to pca domain +OutAxx = InAxx ; +temp = W*reshape(permute(InAxx.Cos,[2,1,3]),size(InAxx.Cos,2),[]); +OutAxx.Cos = permute(reshape(temp,size(InAxx.Cos,2),size(InAxx.Cos,1),size(InAxx.Cos,3)),[2,1,3]); +temp = W*reshape(permute(InAxx.Sin,[2,1,3]),size(InAxx.Sin,2),[]); +OutAxx.Sin = permute(reshape(temp,size(InAxx.Sin,2),size(InAxx.Sin,1),size(InAxx.Sin,3)),[2,1,3]); +OutAxx.Amp = abs(OutAxx.Cos +i *OutAxx.Sin); + + + + From 6ae80a8efcff69aab12ae43ab02c9246b42f5c9e Mon Sep 17 00:00:00 2001 From: Sebastian Bosse Date: Fri, 10 Aug 2018 11:51:03 +0200 Subject: [PATCH 06/62] added ssd --- +mrC/+SpatialFilters/SSD.m | 52 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 +mrC/+SpatialFilters/SSD.m diff --git a/+mrC/+SpatialFilters/SSD.m b/+mrC/+SpatialFilters/SSD.m new file mode 100644 index 0000000..0cdc505 --- /dev/null +++ b/+mrC/+SpatialFilters/SSD.m @@ -0,0 +1,52 @@ +function [OutAxx,W,A] = SSD(InAxx,freqs) + +% This function calculates finds a spatial filter based on the the SSD of +% EEGAxx. The signal is assumed in freqs, noise is assumed in the +% neighboring bins + +% INPUT: + % InAxx: EEG data in Axx format + % freqs: Frequnecies of signal considered +% OUTPUT: + % OutAxx: Data in component space in Axx format + % W: Spatial filter + % A: Activation pattern + +% Written by Sebastian Bosse, 3.8.2018 + + + freq_range = 0:InAxx.dFHz:InAxx.dFHz*(InAxx.nFr-1); + freq_idxs = find(ismember(freq_range,freqs)); + + real_part_signal = InAxx.Cos(freq_idxs,:,:); + imag_part_signal = InAxx.Sin(freq_idxs,:,:); + cmplx_signal = cat(1, real_part_signal,real_part_signal) ... % even real part + + cat(1, -imag_part_signal,imag_part_signal); % odd imag part + + real_part_noise = cat(1,InAxx.Cos(freq_idxs-1,:,:),InAxx.Cos(freq_idxs+1,:,:)); + imag_part_noise = cat(1,InAxx.Sin(freq_idxs-1,:,:),InAxx.Sin(freq_idxs+1,:,:)); + cmplx_noise = cat(1, real_part_noise,real_part_noise) ... % even real part + + cat(1, -imag_part_noise,imag_part_noise); % odd imag part + + C_s =conj(reshape(permute(cmplx_signal,[2,1,3]),size(cmplx_signal,2),[]))*(reshape(permute(cmplx_signal,[2,1,3]),size(cmplx_signal,2),[])'); + C_n =conj(reshape(permute(cmplx_noise,[2,1,3]),size(cmplx_noise,2),[]))*(reshape(permute(cmplx_noise,[2,1,3]),size(cmplx_noise,2),[])'); + + %TODO: there should be some kind of prior dimensionality reduction if the + %covariance matrix is rank deficient + + [W,D] =eig(C_s,C_s+C_n); + [D,sorted_idx] = sort(diag(D),'descend') ; + W = W(:,sorted_idx); + A = C_s * W / (W'*C_s*W); + + % project to pca domain + OutAxx = InAxx ; + temp = W*reshape(permute(InAxx.Cos,[2,1,3]),size(InAxx.Cos,2),[]); + OutAxx.Cos = permute(reshape(temp,size(InAxx.Cos,2),size(InAxx.Cos,1),size(InAxx.Cos,3)),[2,1,3]); + temp = W*reshape(permute(InAxx.Sin,[2,1,3]),size(InAxx.Sin,2),[]); + OutAxx.Sin = permute(reshape(temp,size(InAxx.Sin,2),size(InAxx.Sin,1),size(InAxx.Sin,3)),[2,1,3]); + OutAxx.Amp = abs(OutAxx.Cos +i *OutAxx.Sin); + +end + + From 35656dcdad74bbfe28201ad18bbb5ea0ec5453ae Mon Sep 17 00:00:00 2001 From: Sebastian Bosse Date: Fri, 10 Aug 2018 11:51:22 +0200 Subject: [PATCH 07/62] added examples script --- .../simulate_example_with_multiple_trials.m | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 Examples/simulate_example_with_multiple_trials.m diff --git a/Examples/simulate_example_with_multiple_trials.m b/Examples/simulate_example_with_multiple_trials.m new file mode 100644 index 0000000..dcc34c3 --- /dev/null +++ b/Examples/simulate_example_with_multiple_trials.m @@ -0,0 +1,84 @@ +% This script modifies the script simulate_examples.m in order to test some +% new developments. +% for this script to run correctly you need three paths: + % mrCpath: The latest mrC package + % ProjectPath: Pointing to the mrC project folder + % AnatomyPath: pointing to the folder where anatomy data is + +% Elham Barzegaran 3/14/2018 +% Modified by Sebastian Bosse: 8/2/2018 + +%% Add latest mrC +clear;clc +mrCFolder = fileparts(fileparts(mfilename('fullpath')));%'/Users/kohler/code/git'; +%mrCFolder = '/Users/bosse/svn_dev/mrC_branched/mrC/' ; +addpath(genpath(mrCFolder)); + +%addpath(genpath('C:\Users\Elhamkhanom\Documents\Codes\Git\surfing'));% this tool can be found in github + +%% mrC project and anatomy paths +% THIS PART OF SCRIPT IS FOR THE LAB COMPUTER, IF YOU ARE USING AN OUT OF STANFORD NETWORK +% COMPUTER, COMMENT THIS PART AND ONLY SET DestPath TO THE FOLDER YOU HAVE +% SAVED THE SAMPLE ANATOMY AND PROJECT FOLDER. YOU CAN DOWNLOAD THOSE FILES +% FROM MY DROPBOX: https://www.dropbox.com/sh/ypjrwjjw3003c2f/AABYO1JEjcdwkH3auBOon6UVa?dl=0 +% + %DestPath = fullfile(mrCFolder,'Examples','ExampleData'); + DestPath = '/export/data/eeg_simulation'; +% +% ProjectPath ='/Volumes/svndl/mrC_Projects/kohler/SYM_RT_LOCKED/SOURCE'; +% % This is to make a portable copy of the project data (both anatomy and forward) +% [ProjectPath, AnatomyPath] = mrC.Simulate.PrepareProjectSimulate(ProjectPath,DestPath ,'FwdFormat','mat'); +% +% ProjectPath = '/Volumes/svndl/mrC_Projects/kohler/SYM_16GR/SOURCE'; +% % This is to make a portable copy of the project data (both anatomy and forward) +% mrC.Simulate.PrepareProjectSimulate(ProjectPath,DestPath ,'FwdFormat','mat'); +% +% +% ProjectPath = '/Volumes/svndl/mrC_Projects/Att_disc_annulus/Source'; +% % This is to make a portable copy of the project data (both anatomy and forward) +% mrC.Simulate.PrepareProjectSimulate(ProjectPath,DestPath ,'FwdFormat','mat'); + +%% Indicate project and anatomy folders and get ROIs for example subjects +% So far, 16 subject have Wang atlas ROIs in this dataset + +AnatomyPath = fullfile(DestPath,'anatomy'); +ProjectPath = fullfile(DestPath,'FwdProject2'); + +% Pre-select ROIs +[RoiList,subIDs] = mrC.Simulate.GetRoiClass(ProjectPath,AnatomyPath);% 13 subjects with Wang atlab +Wangs = cellfun(@(x) {x.getAtlasROIs('wang')},RoiList); +Wangnums = cellfun(@(x) x.ROINum,Wangs)>0; + +%% SSVEP signal can be simulated using ModelSourceSignal with defined parameters, otherwise Roisignal function will generate a default two source SSVEP signal +% a simple SSVEP signal... + +[outSignal, FundFreq, SF]= mrC.Simulate.ModelSeedSignal('signalType','SSVEP','signalFreq',[2 3],'signalHarmonic',{[2,0,1],[1,1,0]},'signalPhase',{[.1,0,.2],[0,.3,0]}); + + +%% simulating EEGs with different ROIs as different conditions +Noise.mu=2; +Noise.lambda = 1/length(outSignal); + +%--------------------------Cond1: V2d_R, V3d_L----------------------------- +Rois1 = cellfun(@(x) x.searchROIs('V2d','wang','R'),RoiList,'UniformOutput',false);% % wang ROI +Rois2 = cellfun(@(x) x.searchROIs('V3d','wang','L'),RoiList,'UniformOutput',false);% % wang ROI +RoisI = cellfun(@(x,y) x.mergROIs(y),Rois1,Rois2,'UniformOutput',false); +[EEGData1,EEGAxx1,~,masterList1,subIDs1] = mrC.Simulate.SimulateProject(ProjectPath,'anatomyPath',AnatomyPath,'signalArray',outSignal,'signalFF',FundFreq,'signalsf',SF,'NoiseParams',Noise,'rois',RoisI,'Save',true,'cndNum',1,'nTrials',40); +close all; + +%--------------------------Cond2: V1d_L, V2d_L----------------------------- +Rois1 = cellfun(@(x) x.searchROIs('V1d','wang','L'),RoiList,'UniformOutput',false);% % wang ROI +Rois2 = cellfun(@(x) x.searchROIs('V2d','wang','L'),RoiList,'UniformOutput',false);% % wang ROI +RoisI = cellfun(@(x,y) x.mergROIs(y),Rois1,Rois2,'UniformOutput',false); +[EEGData2,EEGAxx2,~,masterList2,subIDs2] = mrC.Simulate.SimulateProject(ProjectPath,'anatomyPath',AnatomyPath,'signalArray',outSignal,'signalFF',FundFreq,'signalsf',SF,'NoiseParams',Noise,'rois',RoisI,'Save',true,'cndNum',2,'nTrials',40); +close all; + +%% PCA +[PCAAxx,W,A] = mrC.SpatialFilters.PCA(EEGAxx1{1}); +MASDEEG_comp = mean(PCAAxx.Amp,3); +mrC.Simulate.PlotEEG(MASDEEG_comp,freq,[],'average over all ',masterListP,fundfreq(1:numel(masterListP)),[],[],[],[],[],A); + +%% SSD +[PCAAxx,W,A] = mrC.SpatialFilters.SSD(EEGAxx1{1},[2,3]); +MASDEEG_comp = mean(PCAAxx.Amp,3); +mrC.Simulate.PlotEEG(MASDEEG_comp,freq,[],'average over all ',masterListP,fundfreq(1:numel(masterListP)),[],[],[],[],[],A); \ No newline at end of file From 7c6b2e2c56c531adcea071f6f30bdcbff26fea55 Mon Sep 17 00:00:00 2001 From: EBarzegaran Date: Fri, 24 Aug 2018 15:56:51 -0700 Subject: [PATCH 08/62] update plot function and add between trials signal variation --- +mrC/+Simulate/GenerateNoise_2.m | 147 +++++++++++++++++++++++++++++++ +mrC/+Simulate/SimulateProject.m | 31 +++++-- +mrC/+Simulate/SrcSigMtx.m | 5 +- 3 files changed, 172 insertions(+), 11 deletions(-) create mode 100644 +mrC/+Simulate/GenerateNoise_2.m diff --git a/+mrC/+Simulate/GenerateNoise_2.m b/+mrC/+Simulate/GenerateNoise_2.m new file mode 100644 index 0000000..3ac31bf --- /dev/null +++ b/+mrC/+Simulate/GenerateNoise_2.m @@ -0,0 +1,147 @@ +function [noise, pink_noise, alpha_noise] = GenerateNoise_2(f_sampling, n_samples, n_nodes, mu, alpha_nodes, noise_mixing_data, spatial_normalization_type,nTrials) +% Syntax: [noise, pink_noise, pink_noise_uncoh, alpha_noise] = GenerateNoise(f_sampling, n_samples, n_nodes, mu, alpha_nodes, noise_mixing_data, spatial_normalization_type) +% Desciption: GENERATE_NOISE Returns noise of unit variance as a combination of alpha +% activity (bandpass filtered white noise) and spatially coherent pink +% noise (spectrally shaped white noise) +% INPUT: + % f_sampling: sampling frequency + % n_samples: number of temporal samples to be generated + % n_nodes: number of nodes/vertices to be generated + % mu: power of pink noise/power of alpha activity ('noise-to-noise' ratio) + % alpha_nodes: indices of nodes/vertices carrying alpha activity + % noise_mixing_data: data necessary to impose a statistical spatial + % relation on the poink noise + % spatial_normalization_type: spatial reference to normalize the + % different noises to + % 'all_nodes': normalize to total number of nodes + % 'active_nodes': normalize only to nodes where a specific noise has any activity +% OUTPUT: + % noise: returns a matrix of size [n_samples,n_nodes] + % pink_noise + % .. + +% Author: Sebastian Bosse +% Latest Modification: EB, 07/17/2018 + +%% ---------------------------- generate alpha noise------------------------ + disp(['Alpha noise...']); + alpha_noise = zeros(n_samples,n_nodes,nTrials); + alpha_noise(:,alpha_nodes,:) = permute(repmat(GetAlphaActivity(n_samples,f_sampling,[8,12],nTrials),[1,1,length(alpha_nodes )]),[1 3 2]); + + if strcmp(spatial_normalization_type,'active_nodes') + alpha_noise = arrayfun(@(x) length(alpha_nodes)*alpha_noise(:,:,x)/norm(alpha_noise(:,:,x),'fro'),1:nTrials,'uni',false); + elseif strcmp(spatial_normalization_type,'all_nodes') + alpha_noise = arrayfun(@(x) alpha_noise(:,:,x)/norm(alpha_noise(:,:,x),'fro'),1:nTrials,'uni',false); + else + error('%s is not implemented as spatial normalization method', spatial_normalization_type) + end + alpha_noise = cat(3,alpha_noise{:}); + +%% -----------------------------generate pink noise------------------------ + disp('Pink noise...'); + [pink_noise_spec, pink_noise] = GetPinkNoise(n_samples, n_nodes,nTrials,false); + + % impose coherence on pink noise + disp('Impose coherence...'); + if strcmp(noise_mixing_data.mixing_type,'coh') % just in case we want to add other mixing mechanisms + % force noise to be spatially coherent within 'hard' frequency + % ranges + % for details see: DOI: 10.1121/1.2987429 + f = [-0.5:1/n_samples:0.5-1/n_samples]*f_sampling; % frequncy range + + % pink_noise_spec2 = fft(pink_noise,[],1); % I directly work with pink_noise_spec + for band_idx = 1:length(noise_mixing_data.band_freqs) + % calc coherence for band + C = noise_mixing_data.matrices{band_idx}; + freq_bin_idxs = (noise_mixing_data.band_freqs{band_idx}(1) Date: Fri, 24 Aug 2018 16:01:36 -0700 Subject: [PATCH 09/62] update plot function --- +mrC/+Simulate/PlotEEG.m | 206 ++++++++++++++++++++++++--------------- 1 file changed, 125 insertions(+), 81 deletions(-) diff --git a/+mrC/+Simulate/PlotEEG.m b/+mrC/+Simulate/PlotEEG.m index dc31d5a..4c07b6c 100644 --- a/+mrC/+Simulate/PlotEEG.m +++ b/+mrC/+Simulate/PlotEEG.m @@ -1,4 +1,4 @@ -function h = PlotEEG(ASDEEG,Freq,savepath,subID, masterList,signalFF,SignalType,jcolors,Mode,EOI,FOI,A) +function h = PlotEEG(ASDEEG,Freq,savepath,subID, masterList,signalFF,SignalType,jcolors,Mode,EOI,FOI,A,W) % This function provides a dual plot of electrode amplitude/phase spectrum and topographic % map of amplitude/phase at a specific frequency (similar to powerDiva) @@ -24,9 +24,10 @@ % FOI: the frequency to plot the (initial) spectrum topo map -% Written by ELham Barzegaran,3.7.2018 -% Latest modification: 6.6.2018 +% Written by Elham Barzegaran,3.7.2018 +% modified by EB: 6.6.2018 % modified by sebastian bosse 8.8.2018 +% modified by EB: 8.21.2018 %% set parameters if ~exist('SignalType','var')|| isempty(SignalType) % plot phase or amp @@ -68,7 +69,9 @@ space = 'chann'; else space = 'comp'; - EOI = 1 ; % start with the first component + EOI = 1; % start with the first component + Mode = ''; + weight = 1; end %% Plot prepration @@ -104,12 +107,33 @@ end set(gca,'tag','info'); + +%% draw buttons for component selection +if strcmpi(space,'comp') + ax1 = axes('Units','pixel','Position',[30 400 50 10],'Visible','off'); + axes(ax1); + text(0, 0,'RC number','fontweight','bold') + popup = uicontrol('Style', 'popup',... + 'String', '1|2|3|4|5|6|7|8|9',... + 'Position', [20 340 100 50],... + 'Callback',@set_component); + % plot which weight + ax2 = axes('Units','pixel','Position',[30 360 50 10],'Visible','off'); + axes(ax2); + text(0, 0,'Weight','fontweight','bold') + popup = uicontrol('Style', 'popup',... + 'String', 'A|W',... + 'Position', [20 300 100 50],... + 'Callback',@set_weight); +end + + %% Loop over plots N = 1; I = 1; while(I) - if strcmp(SignalType,'Amplitude') + if strcmpi(SignalType,'Amplitude') if strcmpi(space,'comp') colorbarLimits = [-0 max(A(:,EOI)*ASDEEG(FOI,EOI)')]; else @@ -117,97 +141,117 @@ else colorbarLimits = [-0 max(ASDEEG(:))]; end end - elseif strcmp(SignalType,'Phase') + elseif strcmpi(SignalType,'Phase') colorbarLimits = [min(ASDEEG(:)) max(ASDEEG(:))]; end - if exist('sp1','var'),delete(sp1);end % topography map plot - sp1 = subplot(1,2,1); - if strcmpi(Mode,'Interact') && ~strcmpi(space,'comp'), - mrC.plotOnEgi(ASDEEG(FOI,:)',colorbarLimits,false,EOI,false,Probs); - else - if strcmpi(space,'comp') - mrC.plotOnEgi(A(:,EOI)*ASDEEG(FOI,EOI)',colorbarLimits,false,[],false,Probs); - else - mrC.plotOnEgi(ASDEEG(FOI,:)',colorbarLimits,false,[],false,Probs); - end - end - title(['Frequency = ' num2str(Freq(FOI)) 'Hz'],'fontsize',FS); - set(sp1,'tag',num2str(1)); - colormap(conMap); - colorbar; - - if exist('sp2','var'),delete(sp2);end % spectrum plot - sp2 = subplot(2,2,4); - bar(Freq(1:Fmax), ASDEEG(1:Fmax,EOI),.15); - xlim([Freq(1) Freq(Fmax)]); - xlabel('Frequency(Hz)','fontsize',FS-2); - if strcmp(SignalType,'Amplitude') - ylim([0 max(ASDEEG(1:Fmax,EOI))*1.1]); - ylabel('ASD','fontsize',FS-2); - elseif strcmp(SignalType,'Phase') - ylim([min(ASDEEG(1:Fmax,EOI))*1.1 max(ASDEEG(1:Fmax,EOI))*1.1]); - ylabel('Phase(degree)','fontsize',FS-2); - end - set(sp2,'tag',num2str(2)); - hold on; - bar(Freq(FOI), ASDEEG(FOI,EOI),.4,'FaceColor','g','EdgeColor','g'); - - % draw buttons for channel selection - if strcmpi(space,'comp') - popup = uicontrol('Style', 'popup',... - 'String', string(1:size(A,1)),... - 'Position', [20 340 100 50],... - 'Callback',@set_component); - end + %---------------------------HEAD PLOT---------------------------------- + plot_topog(); + %---------------------------SPECTRUM PLOT------------------------------ + plot_spectrum(); + %---------------------------------------------------------------------- if strcmpi(space,'comp') title(['Component ' num2str(num2str(EOI))],'fontsize',FS); else title(['Electrode ' num2str(num2str(EOI))],'fontsize',FS); end - %% Reads keyboard or mouse click + %% ----------------Reads keyboard or mouse click----------------------- if strcmpi(Mode,'Interact') - w = waitforbuttonpress; - switch w - case 1 % keyboard - key = get(h,'currentcharacter'); - if key==27 % (Esc key) -> close the plot and return - close; - h=[]; - return - elseif key==13 % (Enter key) -> save the current figure - set(h, 'PaperPosition',[1 1 12 5]); - print(fullfile(savepath,['SimEEG_Subject' subID 'Electrode' num2str(EOI) '_Freq' num2str(Freq(FOI)) 'Hz.tif']),'-dtiff','-r300');% Later I can update this to contain the simulation parameters - elseif strcmp(key,'n')||strcmp(key,'N') % if n or N is pressed -> change head plot normalization - if N==1, N=0; else N=1;end - end - - case 0 % mouse click - mousept = get(gca,'currentPoint'); - SPI = get(gca,'tag'); - x = mousept(1,1); - y = mousept(1,2); - % update location - switch SPI - case '1' - if ~strcmpi(space,'comp') - Epos2= repmat([x y],[128 1]); - dis = sqrt(sum((tEpos-Epos2).^2,2)); - [~,EOI] = min(dis); - end - case '2' - [~,FOI] = min(abs(repmat(x,[1 size(ASDEEG,1)])-Freq)); - end - end + w = waitforbuttonpress; + switch w + case 1 % keyboard + key = get(h,'currentcharacter'); + if key==27 % (Esc key) -> close the plot and return + close; + h=[]; + return + elseif key==13 % (Enter key) -> save the current figure + set(h, 'PaperPosition',[1 1 12 5]); + print(fullfile(savepath,['SimEEG_Subject' subID 'Electrode' num2str(EOI) '_Freq' num2str(Freq(FOI)) 'Hz.tif']),'-dtiff','-r300');% Later I can update this to contain the simulation parameters + elseif strcmp(key,'n')||strcmp(key,'N') % if n or N is pressed -> change head plot normalization + if N==1, N=0; else N=1;end + end + + case 0 % mouse click + mousept = get(gca,'currentPoint'); + SPI = get(gca,'tag'); + x = mousept(1,1); + y = mousept(1,2); + % update location + switch SPI + case '1' + if ~strcmpi(space,'comp') + Epos2= repmat([x y],[128 1]); + dis = sqrt(sum((tEpos-Epos2).^2,2)); + [~,EOI] = min(dis); + end + case '2' + [~,FOI] = min(abs(repmat(x,[1 size(ASDEEG,1)])-Freq)); + end + end else I = 0; end end - + %---------------------------------------------------------------------- function set_component(source,event) - EOI = source.Value ; + EOI = get(source,'value'); + plot_topog(); + plot_spectrum(); + end + + function set_weight(source,event) + weight = get(source,'value'); + plot_topog(); + + end +%-------------------------------------------------------------------------- + function plot_topog() + % topography map plot + + sp1 = subplot(1,2,1);delete(sp1); + sp1 = subplot(1,2,1); + if strcmpi(Mode,'Interact') && ~strcmpi(space,'comp'), + mrC.plotOnEgi(ASDEEG(FOI,:)',colorbarLimits,false,EOI,false,Probs); + else + if strcmpi(space,'comp') + if weight==1 + conMap = jmaColors('hotcortex'); + mrC.plotOnEgi(A(:,EOI)*ASDEEG(FOI,EOI)',colorbarLimits,false,[],false,Probs); + + else + mrC.plotOnEgi(W(:,EOI),[-max(abs(W(:,EOI))) max(abs(W(:,EOI)))],false,[],false,Probs); + conMap = jmaColors('coolhotcortex'); + end + else + mrC.plotOnEgi(ASDEEG(FOI,:)',colorbarLimits,false,[],false,Probs); + end + end + title(['Frequency = ' num2str(Freq(FOI)) 'Hz'],'fontsize',FS); + set(sp1,'tag',num2str(1)); + colormap(conMap); + colorbar; end +%-------------------------------------------------------------------------- + function plot_spectrum() + % spectrum plot + SPEC = ASDEEG(1:Fmax,EOI); + sp2 = subplot(2,2,4); delete(sp2); + sp2 = subplot(2,2,4); + bar(Freq(1:Fmax), SPEC,.15); + xlim([Freq(1) Freq(Fmax)]); + xlabel('Frequency(Hz)','fontsize',FS-2); + if strcmp(SignalType,'Amplitude') + ylim([min(SPEC) max(SPEC)*1.1]); + ylabel('ASD','fontsize',FS-2); + elseif strcmp(SignalType,'Phase') + ylim([min(SPEC)*1.1 max(SPEC)*1.1]); + ylabel('Phase(degree)','fontsize',FS-2); + end + set(sp2,'tag',num2str(2)); + + if strcmpi(Mode ,'Interact'),hold on; bar(Freq(FOI), SPEC(FOI),.4,'FaceColor','g','EdgeColor','g'); end -%print(fullfile(savepath,['SimEEG_Subject' subID 'Electrode' num2str(EOI) '_Freq' num2str(Freq(FOI)) 'Hz_' SignalType '.tif']),'-dtiff','-r300');% Later I can update this to contain the simulation parameters + end end \ No newline at end of file From 7700fb4be00dd380109ab833f9ca18e34bb3daee Mon Sep 17 00:00:00 2001 From: Sebastian Bosse Date: Tue, 11 Sep 2018 16:13:07 +0200 Subject: [PATCH 10/62] improved visualization in component space added control using keyboard --- +mrC/+Simulate/PlotEEG.m | 83 +++++++++++++++++++++++++++------------- 1 file changed, 57 insertions(+), 26 deletions(-) diff --git a/+mrC/+Simulate/PlotEEG.m b/+mrC/+Simulate/PlotEEG.m index dc31d5a..560baa7 100644 --- a/+mrC/+Simulate/PlotEEG.m +++ b/+mrC/+Simulate/PlotEEG.m @@ -154,12 +154,6 @@ bar(Freq(FOI), ASDEEG(FOI,EOI),.4,'FaceColor','g','EdgeColor','g'); % draw buttons for channel selection - if strcmpi(space,'comp') - popup = uicontrol('Style', 'popup',... - 'String', string(1:size(A,1)),... - 'Position', [20 340 100 50],... - 'Callback',@set_component); - end if strcmpi(space,'comp') title(['Component ' num2str(num2str(EOI))],'fontsize',FS); else @@ -180,34 +174,71 @@ print(fullfile(savepath,['SimEEG_Subject' subID 'Electrode' num2str(EOI) '_Freq' num2str(Freq(FOI)) 'Hz.tif']),'-dtiff','-r300');% Later I can update this to contain the simulation parameters elseif strcmp(key,'n')||strcmp(key,'N') % if n or N is pressed -> change head plot normalization if N==1, N=0; else N=1;end + + elseif key==28 && strcmpi(space,'comp') % cursor left + EOI = mod(EOI-1,size(A,2)+1) ; + if EOI==0 % 0 is not valid + EOI = mod(EOI-1,size(A,2)+1) ; + end + elseif key==29 && strcmpi(space,'comp') % cursor right + EOI = mod(EOI+1,size(A,2)+1) ; + if EOI==0 % 0 is not valid + EOI = mod(EOI+1,size(A,2)+1) ; + end + elseif key==30 && strcmpi(space,'comp') % cursor up + EOI = mod(EOI+10,size(A,2)+1) ; + if EOI==0 % 0 is not valid + EOI = mod(EOI+1,size(A,2)+1) ; + end + elseif key==31 && strcmpi(space,'comp') % cursor down + EOI = mod(EOI-10,size(A,2)+1) ; + if EOI==0 % 0 is not valid + EOI = mod(EOI-1,size(A,2)+1) ; + end + elseif key=='a' + FOI = mod(FOI-1,Fmax+1) ; + if FOI==0 % 0 is not valid + FOI = mod(FOI-1,Fmax+1) ; + end + elseif key=='d' + FOI = mod(FOI+1,Fmax+1) ; + if FOI==0 % 0 is not valid + FOI = mod(FOI+1,Fmax+1) ; + end + elseif key=='w' + FOI = mod(FOI+10,Fmax+1) ; + if FOI==0 % 0 is not valid + FOI = mod(FOI+1,Fmax+1) ; + end + elseif key=='s' + FOI = mod(FOI-10,Fmax+1) ; + if FOI==0 % 0 is not valid + FOI = mod(FOI-1,Fmax+1) ; + end end case 0 % mouse click - mousept = get(gca,'currentPoint'); - SPI = get(gca,'tag'); - x = mousept(1,1); - y = mousept(1,2); - % update location - switch SPI - case '1' - if ~strcmpi(space,'comp') - Epos2= repmat([x y],[128 1]); - dis = sqrt(sum((tEpos-Epos2).^2,2)); - [~,EOI] = min(dis); - end - case '2' - [~,FOI] = min(abs(repmat(x,[1 size(ASDEEG,1)])-Freq)); - end + + mousept = get(gca,'currentPoint'); + SPI = get(gca,'tag'); + x = mousept(1,1); + y = mousept(1,2); + % update location + switch SPI + case '1' + if ~strcmpi(space,'comp') + Epos2= repmat([x y],[128 1]); + dis = sqrt(sum((tEpos-Epos2).^2,2)); + [~,EOI] = min(dis); + end + case '2' + [~,FOI] = min(abs(repmat(x,[1 size(ASDEEG,1)])-Freq)); + end end else I = 0; end end - function set_component(source,event) - EOI = source.Value ; - end - - %print(fullfile(savepath,['SimEEG_Subject' subID 'Electrode' num2str(EOI) '_Freq' num2str(Freq(FOI)) 'Hz_' SignalType '.tif']),'-dtiff','-r300');% Later I can update this to contain the simulation parameters end \ No newline at end of file From f547aeedfda4734aca519ffb9a6009463bf3506c Mon Sep 17 00:00:00 2001 From: Sebastian Bosse Date: Tue, 11 Sep 2018 16:28:22 +0200 Subject: [PATCH 11/62] bugfix: corrected indices of frequency bands --- +mrC/+Simulate/GenerateNoise.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/+mrC/+Simulate/GenerateNoise.m b/+mrC/+Simulate/GenerateNoise.m index e65fb89..b035a68 100644 --- a/+mrC/+Simulate/GenerateNoise.m +++ b/+mrC/+Simulate/GenerateNoise.m @@ -47,7 +47,7 @@ % force noise to be spatially coherent within 'hard' frequency % ranges % for details see: DOI: 10.1121/1.2987429 - f = [-0.5:1/n_samples:0.5-1/n_samples]*f_sampling; % frequncy range + f = ifft([-0.5:1/n_samples:0.5-1/n_samples]*f_sampling); % frequncy range pink_noise_spec = fft(pink_noise,[],1); for band_idx = 1:length(noise_mixing_data.band_freqs) From 99f3bde291ecf77e738474621d397926031d6056 Mon Sep 17 00:00:00 2001 From: Sebastian Bosse Date: Wed, 12 Sep 2018 12:25:12 +0200 Subject: [PATCH 12/62] minor changes to improve speed --- +mrC/+Simulate/GenerateNoise.m | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/+mrC/+Simulate/GenerateNoise.m b/+mrC/+Simulate/GenerateNoise.m index b035a68..8042571 100644 --- a/+mrC/+Simulate/GenerateNoise.m +++ b/+mrC/+Simulate/GenerateNoise.m @@ -54,7 +54,7 @@ % calc coherence for band C = noise_mixing_data.matrices{band_idx}; freq_bin_idxs = (noise_mixing_data.band_freqs{band_idx}(1) Date: Mon, 17 Sep 2018 20:37:42 +0200 Subject: [PATCH 13/62] unified parameters for function bugfixes in implementation --- +mrC/+SpatialFilters/PCA.m | 60 ++++++++++++++++++++------------------ 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/+mrC/+SpatialFilters/PCA.m b/+mrC/+SpatialFilters/PCA.m index ad369b3..efab8a9 100644 --- a/+mrC/+SpatialFilters/PCA.m +++ b/+mrC/+SpatialFilters/PCA.m @@ -1,11 +1,14 @@ -function [OutAxx,W,A] = PCA(InAxx,freqs) +function [OutAxx,W,A,D] = PCA(InAxx,varargin) % This function calculates finds a spatial filter based on the the PCA of -% EEGAxx. +% InAxx. % INPUT: % InAxx: EEG data in Axx format - % freqs: Frequnecies considered +% : + % freq_range: Frequnecies of signal considered. + % Default: all, except 0 Hz + % OUTPUT: % OutAxx: Data in component space in Axx format % W: Spatial filter @@ -13,45 +16,44 @@ % Written by Sebastian Bosse, 3.8.2018 -if exist('freqs','var') && ~isempty(freqs) - freq_range = 0:InAxx.dFHz:InAxx.dFHz*(InAxx.nFr-1); - freq_idxs = find(ismember(freq_range,freqs)); - - real_part = InAxx.Cos(freq_idxs,:,:); - imag_part = InAxx.Sin(freq_idxs,:,:); +opt = ParseArgs(varargin,... + 'freq_range', InAxx.dFHz*[1:(InAxx.nFr-1)] ... + ); + + +freq_idxs = 1+opt.freq_range/InAxx.dFHz ; % shift as 0Hz has idx 1 + +if freq_idxs(1)==1 % 0Hz is considered + cmplx_signal = cat(1, InAxx.Cos(freq_idxs(end:-1:2),:,:),InAxx.Cos(freq_idxs,:,:)) ... % even real part + + 1i*cat(1, -InAxx.Sin(freq_idxs(end:-1:2),:,:),InAxx.Sin(freq_idxs,:,:)); % odd imag part - if ismember(0,freqs) - cmplx_signal = cat(1, real_part,real_part) ... % even real part - + cat(1, -imag_part,imag_part); % odd imag part - else - cmplx_signal = cat(1, real_part,InAxx.Cos(1,:,:),real_part) ... % even real part - + cat(1, -imag_part,InAxx.Sin(1,:,:),imag_part); % odd imag part - end -else % just take it all - cmplx_signal = cat(1, InAxx.Cos(2:end,:,:),InAxx.Cos) ... % even real part - + cat(1, -InAxx.Sin(2:end,:,:),InAxx.Sin); % odd imag part +else + cmplx_signal = cat(1, InAxx.Cos(freq_idxs(end:-1:1),:,:),InAxx.Cos(freq_idxs,:,:)) ... % even real part + + 1i*cat(1, -InAxx.Sin(freq_idxs(end:-1:1),:,:),InAxx.Sin(freq_idxs,:,:)); % odd imag part end -% C = zeros(size(cmplx_signal,2)) ; -% for trial_idx = 1:size(cmplx_signal,3) -% C = C+cmplx_signal(:,:,trial_idx)'*conj(cmplx_signal(:,:,trial_idx)) ; -% end -% more efficient by avoiding the loop -C =(reshape(permute(cmplx_signal,[2,1,3]),size(cmplx_signal,2),[]))*conj(reshape(permute(cmplx_signal,[2,1,3]),size(cmplx_signal,2),[])'); + +C =reshape(permute(cmplx_signal,[2,1,3]),size(cmplx_signal,2),[])*conj(reshape(permute(cmplx_signal,[2,1,3]),size(cmplx_signal,2),[]))'; + +if sum(abs(imag(C(:))))/sum(abs(real(C(:))))>10^-10 + error('PCA: Covariance matrix is complex') +end +C = real(C); + [W,D] = eig(C); [D,sorted_idx] = sort(diag(D),'descend') ; W = W(:,sorted_idx); -A = C * W / (W'*C*W); +A = C * W * pinv(W'*C*W); % project to pca domain OutAxx = InAxx ; -temp = W*reshape(permute(InAxx.Cos,[2,1,3]),size(InAxx.Cos,2),[]); +temp = W'*reshape(permute(InAxx.Cos,[2,1,3]),size(InAxx.Cos,2),[]); OutAxx.Cos = permute(reshape(temp,size(InAxx.Cos,2),size(InAxx.Cos,1),size(InAxx.Cos,3)),[2,1,3]); -temp = W*reshape(permute(InAxx.Sin,[2,1,3]),size(InAxx.Sin,2),[]); +temp = W'*reshape(permute(InAxx.Sin,[2,1,3]),size(InAxx.Sin,2),[]); OutAxx.Sin = permute(reshape(temp,size(InAxx.Sin,2),size(InAxx.Sin,1),size(InAxx.Sin,3)),[2,1,3]); -OutAxx.Amp = abs(OutAxx.Cos +i *OutAxx.Sin); +OutAxx.Amp = abs(OutAxx.Cos +1i *OutAxx.Sin); From 13d8d691e367945525cedf0697b9457d25acfa3c Mon Sep 17 00:00:00 2001 From: Sebastian Bosse Date: Mon, 17 Sep 2018 20:38:52 +0200 Subject: [PATCH 14/62] unified function parameters bugfixes in implementation --- +mrC/+SpatialFilters/SSD.m | 94 ++++++++++++++++++++++++++------------ 1 file changed, 65 insertions(+), 29 deletions(-) diff --git a/+mrC/+SpatialFilters/SSD.m b/+mrC/+SpatialFilters/SSD.m index 0cdc505..fb03431 100644 --- a/+mrC/+SpatialFilters/SSD.m +++ b/+mrC/+SpatialFilters/SSD.m @@ -1,52 +1,88 @@ -function [OutAxx,W,A] = SSD(InAxx,freqs) +function [OutAxx,W,A,D] = SSD(InAxx,freqs,varargin) % This function calculates finds a spatial filter based on the the SSD of -% EEGAxx. The signal is assumed in freqs, noise is assumed in the +% InAxx. The signal is assumed in freqs, noise is assumed in the % neighboring bins % INPUT: % InAxx: EEG data in Axx format % freqs: Frequnecies of signal considered +% : + % do_whitening: Apply whitening and dimensionality deflation prior to + % estimation of spatial filter. Default: true. + % rank_ratio: Threshold for dimensionality reduction based on + % eigenvalue spectrum. Default: 10^-4 % OUTPUT: % OutAxx: Data in component space in Axx format % W: Spatial filter % A: Activation pattern - +% % Written by Sebastian Bosse, 3.8.2018 +opt = ParseArgs(varargin,... + 'do_whitening', true, ... + 'rank_ratio', 10^-4 ... + ); - freq_range = 0:InAxx.dFHz:InAxx.dFHz*(InAxx.nFr-1); - freq_idxs = find(ismember(freq_range,freqs)); - - real_part_signal = InAxx.Cos(freq_idxs,:,:); - imag_part_signal = InAxx.Sin(freq_idxs,:,:); - cmplx_signal = cat(1, real_part_signal,real_part_signal) ... % even real part - + cat(1, -imag_part_signal,imag_part_signal); % odd imag part +freq_range = 0:InAxx.dFHz:InAxx.dFHz*(InAxx.nFr-1); +freq_idxs = find(ismember(freq_range,freqs)); - real_part_noise = cat(1,InAxx.Cos(freq_idxs-1,:,:),InAxx.Cos(freq_idxs+1,:,:)); - imag_part_noise = cat(1,InAxx.Sin(freq_idxs-1,:,:),InAxx.Sin(freq_idxs+1,:,:)); - cmplx_noise = cat(1, real_part_noise,real_part_noise) ... % even real part - + cat(1, -imag_part_noise,imag_part_noise); % odd imag part +real_part_signal = InAxx.Cos(freq_idxs,:,:); +imag_part_signal = InAxx.Sin(freq_idxs,:,:); +cmplx_signal = cat(1, real_part_signal,real_part_signal) ... % even real part + + 1i*cat(1, -imag_part_signal,imag_part_signal); % odd imag part - C_s =conj(reshape(permute(cmplx_signal,[2,1,3]),size(cmplx_signal,2),[]))*(reshape(permute(cmplx_signal,[2,1,3]),size(cmplx_signal,2),[])'); - C_n =conj(reshape(permute(cmplx_noise,[2,1,3]),size(cmplx_noise,2),[]))*(reshape(permute(cmplx_noise,[2,1,3]),size(cmplx_noise,2),[])'); +real_part_noise = cat(1,InAxx.Cos(freq_idxs-1,:,:),InAxx.Cos(freq_idxs+1,:,:)); +imag_part_noise = cat(1,InAxx.Sin(freq_idxs-1,:,:),InAxx.Sin(freq_idxs+1,:,:)); +cmplx_noise = cat(1, real_part_noise,real_part_noise) ... % even real part + + 1i*cat(1, -imag_part_noise,imag_part_noise); % odd imag part + +C_s =conj(reshape(permute(cmplx_signal,[2,1,3]),size(cmplx_signal,2),[]))*(reshape(permute(cmplx_signal,[2,1,3]),size(cmplx_signal,2),[])'); +C_n =conj(reshape(permute(cmplx_noise,[2,1,3]),size(cmplx_noise,2),[]))*(reshape(permute(cmplx_noise,[2,1,3]),size(cmplx_noise,2),[])'); - %TODO: there should be some kind of prior dimensionality reduction if the - %covariance matrix is rank deficient +if sum(abs(imag(C_n(:))))/sum(abs(real(C_n(:))))>10^-10 + error('SSD: Covariance matrix is complex') +end +if sum(abs(imag(C_s(:))))/sum(abs(real(C_s(:))))>10^-10 + error('SSD: Covariance matrix is complex') +end +C_n = real(C_n); +C_s = real(C_s); - [W,D] =eig(C_s,C_s+C_n); - [D,sorted_idx] = sort(diag(D),'descend') ; - W = W(:,sorted_idx); - A = C_s * W / (W'*C_s*W); - % project to pca domain - OutAxx = InAxx ; - temp = W*reshape(permute(InAxx.Cos,[2,1,3]),size(InAxx.Cos,2),[]); - OutAxx.Cos = permute(reshape(temp,size(InAxx.Cos,2),size(InAxx.Cos,1),size(InAxx.Cos,3)),[2,1,3]); - temp = W*reshape(permute(InAxx.Sin,[2,1,3]),size(InAxx.Sin,2),[]); - OutAxx.Sin = permute(reshape(temp,size(InAxx.Sin,2),size(InAxx.Sin,1),size(InAxx.Sin,3)),[2,1,3]); - OutAxx.Amp = abs(OutAxx.Cos +i *OutAxx.Sin); +if opt.do_whitening % and deflate matrix dimensionality + disp('whitening') + [V, D] = eig(C_s+C_n); + [ev, desc_idxs] = sort(diag(D), 'descend'); + V = V(:,desc_idxs); + + dims = sum((ev/sum(ev))>opt.rank_ratio) ; + P = V(:,1:dims)*diag(1./sqrt(ev(1:dims))); +else + dims = size(C_s,1); + P = eye(dims) ; end + +C_s_w = P' * C_s * P; +C_n_w = P' * C_n * P; + +[W,D] =eig(C_s_w,C_n_w); +[D,sorted_idx] = sort(diag(D),'descend') ; +W = W(:,sorted_idx); +W = P * W; + + +A = C_s * W * pinv(W'*C_s*W); + +% project to ssd domain +OutAxx = InAxx ; +temp = W'*reshape(permute(InAxx.Cos,[2,1,3]),size(InAxx.Cos,2),[]); +OutAxx.Cos = permute(reshape(temp,dims,size(InAxx.Cos,1),size(InAxx.Cos,3)),[2,1,3]); +temp = W'*reshape(permute(InAxx.Sin,[2,1,3]),size(InAxx.Sin,2),[]); +OutAxx.Sin = permute(reshape(temp,dims,size(InAxx.Sin,1),size(InAxx.Sin,3)),[2,1,3]); +OutAxx.Amp = abs(OutAxx.Cos +1i *OutAxx.Sin); + + From 795056857bbbad96de8e4116e557de48b12e34e7 Mon Sep 17 00:00:00 2001 From: Sebastian Bosse Date: Mon, 17 Sep 2018 20:39:51 +0200 Subject: [PATCH 15/62] corrected typo --- +mrC/+Simulate/PlotEEG.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/+mrC/+Simulate/PlotEEG.m b/+mrC/+Simulate/PlotEEG.m index 560baa7..e070694 100644 --- a/+mrC/+Simulate/PlotEEG.m +++ b/+mrC/+Simulate/PlotEEG.m @@ -63,12 +63,12 @@ end % allow for visualization of spatial filters -% what do we need? if ~exist('A','var') || isempty(Mode) - space = 'chann'; + space = 'chan'; else space = 'comp'; EOI = 1 ; % start with the first component + A = abs(A) ; % visualization in amplitude domain end %% Plot prepration From 3d499700e0f5a90f694da4a081db25ea3785191e Mon Sep 17 00:00:00 2001 From: Sebastian Bosse Date: Mon, 17 Sep 2018 20:40:11 +0200 Subject: [PATCH 16/62] initial commit --- +mrC/+SpatialFilters/CSP.m | 100 +++++++++++++++++++++++++++++++ +mrC/+SpatialFilters/RCA.m | 119 +++++++++++++++++++++++++++++++++++++ 2 files changed, 219 insertions(+) create mode 100644 +mrC/+SpatialFilters/CSP.m create mode 100644 +mrC/+SpatialFilters/RCA.m diff --git a/+mrC/+SpatialFilters/CSP.m b/+mrC/+SpatialFilters/CSP.m new file mode 100644 index 0000000..3b553e6 --- /dev/null +++ b/+mrC/+SpatialFilters/CSP.m @@ -0,0 +1,100 @@ +function [OutAxxs,W,A,D] = CSP(InAxxs,varargin) + + % This function finds a spatial filter based on the the CSP of + % InAxxs{1} and InAxxs{1}. + % : + % freq_range: Frequnecies of signal considered. + % Default: all, except 0 Hz + % do_whitening: Apply whitening and dimensionality deflation prior to + % estimation of spatial filter. Default: true. + % rank_ratio: Threshold for dimensionality reduction based on + % eigenvalue spectrum. Default: 10^-4 + % OUTPUT: + % OutAxx: Data in component space in Axx format + % W: Spatial filter + % A: Activation pattern + % D: Eigenvalues + % + % Written by Sebastian Bosse, 10.8.2018 + +opt = ParseArgs(varargin,... + 'do_whitening', true, ... + 'rank_ratio', 10^-4, ... + 'freq_range', InAxxs{1}.dFHz*[1:(InAxxs{1}.nFr-1)] ... + ); + +if length(InAxxs)~=2 + error('CSP: CSP not implemented for more classes than 2') +end +if InAxxs{1}.nFr~=InAxxs{2}.nFr + error('CSP: Number of frequency bins not identical in the two given classes') +end +if InAxxs{1}.dFHz~=InAxxs{2}.dFHz + error('CSP: Frequency resolution not identical in the two given classes') +end + +cmplx_signal = cell(length(InAxxs),1) ; + +freq_idxs = 1+opt.freq_range/InAxxs{1}.dFHz ; % shift as 0Hz has idx 1 + +for class_idx = 1:length(InAxxs) + if freq_idxs(1)==1 % 0Hz is considered + cmplx_signal{class_idx} = cat(1, InAxxs{class_idx}.Cos(freq_idxs(2:end),:,:),InAxxs{class_idx}.Cos(freq_idxs,:,:)) ... % even real part + + cat(1, -InAxxs{class_idx}.Sin(freq_idxs(2:end),:,:),InAxxs{class_idx}.Sin(freq_idxs,:,:)); % odd imag part + + else + cmplx_signal{class_idx} = cat(1, InAxxs{class_idx}.Cos(freq_idxs,:,:),InAxxs{class_idx}.Cos(freq_idxs,:,:)) ... % even real part + + cat(1, -InAxxs{class_idx}.Sin(freq_idxs,:,:),InAxxs{class_idx}.Sin(freq_idxs,:,:)); % odd imag part + end + +end + +C = zeros(size(cmplx_signal{1},2), size(cmplx_signal{1},2), length(InAxxs)) ; +% calculate covariance matrices in fourier domain +for class_idx = 1:length(InAxxs) + C(:,:,class_idx) = conj(reshape(permute(cmplx_signal{class_idx},[2,1,3]),size(cmplx_signal{class_idx},2),[]))*(reshape(permute(cmplx_signal{class_idx},[2,1,3]),size(cmplx_signal{class_idx},2),[])'); + % normalize by number of observations + C(:,:,class_idx) = C(:,:,class_idx)/ size(cmplx_signal{class_idx},3); +end + +if opt.do_whitening % and deflate matrix dimensionality + disp('whitening') + [V, D] = eig(sum(C,3)); + [ev, desc_idxs] = sort(diag(D), 'descend'); + V = V(:,desc_idxs); + + dims = sum((ev/sum(ev))>opt.rank_ratio) ; + P = V(:,1:dims)*diag(1./sqrt(ev(1:dims))); +else + dims = size(C,1) ; + P = eye(dims); + +end + +C_w = zeros(size(P,2),size(P,2),length(InAxxs)) ; + +for class_idx = 1:length(InAxxs) + C_w(:,:,class_idx) = P' * C(:,:,class_idx) * P ; +end + +if opt.do_whitening % calculation in 'white space' + [W,D] = eig(C_w(:,:,1)-C_w(:,:,2)) ; +else % calculation in channel space + [W,D] = eig(C_w(:,:,1)-C_w(:,:,2),sum(C_w,3)) ; +end +[D,sorted_idx] = sort(diag(D),'descend') ; +W = W(:,sorted_idx); +W = P * W; + +A = sum(C,3) * W *pinv(W'*sum(C,3)*W); + +OutAxxs = InAxxs ; +for class_idx = 1:length(InAxxs) + % project to csp domain + temp = W'*reshape(permute(InAxxs{class_idx}.Cos,[2,1,3]),size(InAxxs{class_idx}.Cos,2),[]); + OutAxxs{class_idx}.Cos = permute(reshape(temp,dims,size(InAxxs{class_idx}.Cos,1),size(InAxxs{class_idx}.Cos,3)),[2,1,3]); + temp = W'*reshape(permute(InAxxs{class_idx}.Sin,[2,1,3]),size(InAxxs{class_idx}.Sin,2),[]); + OutAxxs{class_idx}.Sin = permute(reshape(temp,dims,size(InAxxs{class_idx}.Sin,1),size(InAxxs{class_idx}.Sin,3)),[2,1,3]); + OutAxxs{class_idx}.Amp = abs(OutAxxs{class_idx}.Cos +1i *OutAxxs{class_idx}.Sin); +end + diff --git a/+mrC/+SpatialFilters/RCA.m b/+mrC/+SpatialFilters/RCA.m new file mode 100644 index 0000000..b9bd73d --- /dev/null +++ b/+mrC/+SpatialFilters/RCA.m @@ -0,0 +1,119 @@ +function [OutAxx,W,A,D] = RCA(InAxx,varargin) + +% This function calculates finds a spatial filter based on the the RCA of +% InAxx. + +% INPUT: + % InAxx: EEG data in Axx format +% : + % freq_range: Frequnecies of signal considered. + % Default: all, except 0 Hz + % do_whitening: Apply whitening and dimensionality deflation prior to + % estimation of spatial filter. Default: true. + % rank_ratio: Threshold for dimensionality reduction based on + % eigenvalue spectrum. Default: 10^-4 +% OUTPUT: + % OutAxx: Data in component space in Axx format + % W: Spatial filter + % A: Activation pattern +% +% Written by Sebastian Bosse, 10.8.2018 + +opt = ParseArgs(varargin,... + 'do_whitening', true, ... + 'rank_ratio', 10^-4, ... + 'freq_range', InAxx.dFHz*[1:(InAxx.nFr-1)], ... + 'model_type','complex' ... + ); + +freq_idxs = 1+opt.freq_range/InAxx.dFHz ; % shift as 0Hz has idx 1 + +n_trials = size(InAxx.Cos,3); +trial_pair_idxs = combnk(1:n_trials,2) ; +trial_pair_idxs = cat(1,trial_pair_idxs,trial_pair_idxs(:,[2,1])); + + +if strcmpi(opt.model_type,'complex') + if freq_idxs(1)==1 % 0Hz is considered + cmplx_signal = cat(1, InAxx.Cos(freq_idxs(2:end),:,:),InAxx.Cos(freq_idxs,:,:)) ... % even real part + + 1i*cat(1, -InAxx.Sin(freq_idxs(2:end),:,:),InAxx.Sin(freq_idxs,:,:)); % odd imag part + else + cmplx_signal = cat(1, InAxx.Cos(freq_idxs,:,:),InAxx.Cos(freq_idxs,:,:)) ... % even real part + + 1i*cat(1, -InAxx.Sin(freq_idxs,:,:),InAxx.Sin(freq_idxs,:,:)); % odd imag part + end + % Note that this is different to Jacek's derivation and implementation as + % it jointly considers real and imaginary values + % Cross-trial correlation + C_xy = conj(reshape(permute(cmplx_signal(:,:,trial_pair_idxs(:,1)),[2,1,3]),size(cmplx_signal,2),[]))*... + reshape(permute(cmplx_signal(:,:,trial_pair_idxs(:,2)),[2,1,3]),size(cmplx_signal,2),[])'/... + size(trial_pair_idxs(:,1),1) ; + % Within-trial correlation + C_xx = conj(reshape(permute(cmplx_signal(:,:,trial_pair_idxs(:,1)),[2,1,3]),size(cmplx_signal,2),[]))*... + reshape(permute(cmplx_signal(:,:,trial_pair_idxs(:,1)),[2,1,3]),size(cmplx_signal,2),[])'/... + size(trial_pair_idxs(:,1),1) ; + C_yy = conj(reshape(permute(cmplx_signal(:,:,trial_pair_idxs(:,2)),[2,1,3]),size(cmplx_signal,2),[]))*... + reshape(permute(cmplx_signal(:,:,trial_pair_idxs(:,2)),[2,1,3]),size(cmplx_signal,2),[])'/... + size(trial_pair_idxs(:,1),1) ; + +elseif strcmpi(opt.model_type,'cartesian') + C_xy = reshape(permute(InAxx.Cos(freq_idxs,:,trial_pair_idxs(:,1)),[2,1,3]),size(InAxx.Cos,2),[])*... + reshape(permute(InAxx.Cos(freq_idxs,:,trial_pair_idxs(:,2)),[2,1,3]),size(InAxx.Cos,2),[])' ; + + C_xx = reshape(permute(InAxx.Cos(freq_idxs,:,trial_pair_idxs(:,1)),[2,1,3]),size(InAxx.Cos,2),[])*... + reshape(permute(InAxx.Cos(freq_idxs,:,trial_pair_idxs(:,1)),[2,1,3]),size(InAxx.Cos,2),[])' ; + + C_yy = reshape(permute(InAxx.Cos(freq_idxs,:,trial_pair_idxs(:,2)),[2,1,3]),size(InAxx.Cos,2),[])*... + reshape(permute(InAxx.Cos(freq_idxs,:,trial_pair_idxs(:,2)),[2,1,3]),size(InAxx.Cos,2),[])' ; +end + + +if sum(abs(imag(C_xx(:))))/sum(abs(real(C_xx(:))))>10^-10 + error('RCA: Covariance matrix is complex') +end +if sum(abs(imag(C_yy(:))))/sum(abs(real(C_yy(:))))>10^-10 + error('RCA: Covariance matrix is complex') +end +if sum(abs(imag(C_xy(:))))/sum(abs(real(C_xy(:))))>10^-10 + error('RCA: Covariance matrix is complex') +end +C_xx = real(C_xx); +C_yy = real(C_yy); +C_xy = real(C_xy); + +if opt.do_whitening % and deflate matrix dimensionality + disp('whitening') + [V, D] = eig(C_xy+C_xx+C_yy); + [ev, desc_idxs] = sort(diag(D), 'descend'); + V = V(:,desc_idxs); + + dims = sum((ev/sum(ev))>opt.rank_ratio) ; + P = V(:,1:dims)*diag(1./sqrt(ev(1:dims))); +else + dims = size(C_xx,1) ; + P = eye(dims ); +end + +C_xx_w = P' * C_xx * P; +C_yy_w = P' * C_yy * P; +C_xy_w = P' * C_xy * P; + +[W,D] = eig(C_xy_w,C_xx_w+C_yy_w) ; + +[D,sorted_idx] = sort(diag(D),'descend') ; +W = W(:,sorted_idx); +W = P * W; +if sum(abs(imag(W)))>10^-10 + error('RCA: W should not be complex!') +else + W = real(W) ; +end + +A = (C_xx+C_yy) * W * pinv(W'*(C_xx+C_yy)*W); + +% project to rca domain +OutAxx = InAxx ; +temp = W'*reshape(permute(InAxx.Cos,[2,1,3]),size(InAxx.Cos,2),[]); +OutAxx.Cos = permute(reshape(temp,dims,size(InAxx.Cos,1),size(InAxx.Cos,3)),[2,1,3]); +temp = W'*reshape(permute(InAxx.Sin,[2,1,3]),size(InAxx.Sin,2),[]); +OutAxx.Sin = permute(reshape(temp,dims,size(InAxx.Sin,1),size(InAxx.Sin,3)),[2,1,3]); +OutAxx.Amp = abs(OutAxx.Cos +1i *OutAxx.Sin); \ No newline at end of file From 788de29b139b7082cc298f09b10d54e622ff5103 Mon Sep 17 00:00:00 2001 From: Sebastian Bosse Date: Mon, 17 Sep 2018 20:42:27 +0200 Subject: [PATCH 17/62] examples for pca, ssd, csp and rca --- .../simulate_example_with_multiple_trials.m | 78 ++++++++++++++++--- 1 file changed, 68 insertions(+), 10 deletions(-) diff --git a/Examples/simulate_example_with_multiple_trials.m b/Examples/simulate_example_with_multiple_trials.m index dcc34c3..43ec26d 100644 --- a/Examples/simulate_example_with_multiple_trials.m +++ b/Examples/simulate_example_with_multiple_trials.m @@ -63,22 +63,80 @@ Rois1 = cellfun(@(x) x.searchROIs('V2d','wang','R'),RoiList,'UniformOutput',false);% % wang ROI Rois2 = cellfun(@(x) x.searchROIs('V3d','wang','L'),RoiList,'UniformOutput',false);% % wang ROI RoisI = cellfun(@(x,y) x.mergROIs(y),Rois1,Rois2,'UniformOutput',false); -[EEGData1,EEGAxx1,~,masterList1,subIDs1] = mrC.Simulate.SimulateProject(ProjectPath,'anatomyPath',AnatomyPath,'signalArray',outSignal,'signalFF',FundFreq,'signalsf',SF,'NoiseParams',Noise,'rois',RoisI,'Save',true,'cndNum',1,'nTrials',40); +[EEGData1,EEGAxx1,~,masterList1,subIDs1] = mrC.Simulate.SimulateProject(ProjectPath,'anatomyPath',AnatomyPath,'signalArray',outSignal,'signalFF',FundFreq,'signalsf',SF,'NoiseParams',Noise,'rois',RoisI,'Save',true,'cndNum',1,'nTrials',100); close all; %--------------------------Cond2: V1d_L, V2d_L----------------------------- Rois1 = cellfun(@(x) x.searchROIs('V1d','wang','L'),RoiList,'UniformOutput',false);% % wang ROI Rois2 = cellfun(@(x) x.searchROIs('V2d','wang','L'),RoiList,'UniformOutput',false);% % wang ROI RoisI = cellfun(@(x,y) x.mergROIs(y),Rois1,Rois2,'UniformOutput',false); -[EEGData2,EEGAxx2,~,masterList2,subIDs2] = mrC.Simulate.SimulateProject(ProjectPath,'anatomyPath',AnatomyPath,'signalArray',outSignal,'signalFF',FundFreq,'signalsf',SF,'NoiseParams',Noise,'rois',RoisI,'Save',true,'cndNum',2,'nTrials',40); +[EEGData2,EEGAxx2,~,masterList2,subIDs2] = mrC.Simulate.SimulateProject(ProjectPath,'anatomyPath',AnatomyPath,'signalArray',outSignal,'signalFF',FundFreq,'signalsf',SF,'NoiseParams',Noise,'rois',RoisI,'Save',true,'cndNum',2,'nTrials',100); close all; +%% -%% PCA -[PCAAxx,W,A] = mrC.SpatialFilters.PCA(EEGAxx1{1}); -MASDEEG_comp = mean(PCAAxx.Amp,3); -mrC.Simulate.PlotEEG(MASDEEG_comp,freq,[],'average over all ',masterListP,fundfreq(1:numel(masterListP)),[],[],[],[],[],A); -%% SSD -[PCAAxx,W,A] = mrC.SpatialFilters.SSD(EEGAxx1{1},[2,3]); -MASDEEG_comp = mean(PCAAxx.Amp,3); -mrC.Simulate.PlotEEG(MASDEEG_comp,freq,[],'average over all ',masterListP,fundfreq(1:numel(masterListP)),[],[],[],[],[],A); \ No newline at end of file +%% +do_whitening = true; +freq = EEGAxx1{1}.dFHz*[0:(EEGAxx1{1}.nFr-1)]; + +sigFreqs = {[2,6],[3,6]} ; +fundFreqs = [2,3] ; +masterListP = [masterList1, masterList2] ; +%% +MASDEEG_raw_comp = mean(EEGAxx1{1}.Amp,3); +mrC.Simulate.PlotEEG(MASDEEG_raw_comp,freq,[],'raw condition 1',masterList1,fundFreqs); +%% +MASDEEG_raw_comp = mean(EEGAxx2{1}.Amp,3); +mrC.Simulate.PlotEEG(MASDEEG_raw_comp,freq,[],'raw condition 2',masterList2,fundFreqs); +%% PCA on full freq range +[PCAAxx,W,A,D] = mrC.SpatialFilters.PCA(EEGAxx1{1},'freq_range',freq); +MASDEEG_pca_comp = mean(PCAAxx.Amp,3); +mrC.Simulate.PlotEEG(MASDEEG_pca_comp,freq,[],'PCA on full freq. range ',masterList1,fundFreqs,[],[],[],[],[],A); + +%% PCA on 1st stim freq +[PCAAxx,W,A,D] = mrC.SpatialFilters.PCA(EEGAxx1{1},'freq_range',fundFreqs(1)); +MASDEEG_pca_comp = mean(PCAAxx.Amp,3); +mrC.Simulate.PlotEEG(MASDEEG_pca_comp,freq,[], sprintf('PCA on %0.1f Hz', fundFreqs(1)), masterList1,fundFreqs,[],[],[],[],[],A); + +%% PCA on 2st stim freq +[PCAAxx,W,A,D] = mrC.SpatialFilters.PCA(EEGAxx1{1},'freq_range',fundFreqs(2)); +MASDEEG_pca_comp = mean(PCAAxx.Amp,3); +mrC.Simulate.PlotEEG(MASDEEG_pca_comp,freq,[], sprintf('PCA on %0.1f Hz', fundFreqs(2)), masterList1,fundFreqs,[],[],[],[],[],A); + +%% SSD on 1st stim freq +[SSDAxx,W,A,D] = mrC.SpatialFilters.SSD(EEGAxx1{1},fundFreqs(1),'do_whitening',true); +MASDEEG_ssd_comp = mean(SSDAxx.Amp,3); +mrC.Simulate.PlotEEG(MASDEEG_ssd_comp,freq,[],sprintf('SSD on %0.1f Hz', fundFreqs(1)),masterList1,fundFreqs,[],[],[],[],[],A); + +%% SSD on 2nd stim freq +[SSDAxx,W,A,D] = mrC.SpatialFilters.SSD(EEGAxx1{1},fundFreqs(2),'do_whitening',true); +MASDEEG_ssd_comp = mean(SSDAxx.Amp,3); +mrC.Simulate.PlotEEG(MASDEEG_ssd_comp,freq,[],sprintf('SSD on %0.1f Hz', fundFreqs(2)),masterList1,fundFreqs,[],[],[],[],[],A); + + +%% CSP +[CSPAxx,W,A,D] = mrC.SpatialFilters.CSP({EEGAxx1{1},EEGAxx2{1}},'freq_range',[2,3],'do_whitening',do_whitening); +class_vis = 1; +MASDEEG_csp_comp = mean(CSPAxx{class_vis}.Amp,3); +mrC.Simulate.PlotEEG(MASDEEG_csp_comp,freq,[],'CSP on fundFreqs ',masterList1,fundFreqs,[],[],[],[],[],A); + +%% CSP +[CSPAxx,W,A,D] = mrC.SpatialFilters.CSP({EEGAxx1{1},EEGAxx2{1}},'do_whitening',do_whitening); +class_vis = 1; +MASDEEG_csp_comp = mean(CSPAxx{class_vis}.Amp,3); +mrC.Simulate.PlotEEG(MASDEEG_csp_comp,freq,[],'CSP on all freqs ',masterList1,fundFreqs,[],[],[],[],[],A); + +%% RCA +[RCAAxx,W,A,D] = mrC.SpatialFilters.RCA(EEGAxx1{1},'freq_range',[2,3],'do_whitening',do_whitening); +MASDEEG_rca_comp = mean(RCAAxx.Amp,3); +mrC.Simulate.PlotEEG(MASDEEG_rca_comp,freq,[],'RCA on fundFreqs ',masterList1,fundFreqs,[],[],[],[],[],A); + +%% RCA +[RCAAxx,W,A,D] = mrC.SpatialFilters.RCA(EEGAxx1{1},'freq_range',[2,3],'do_whitening',do_whitening, 'model_type','cartesian'); +MASDEEG_rca_comp = mean(RCAAxx.Amp,3); +mrC.Simulate.PlotEEG(MASDEEG_rca_comp,freq,[],'RCA on fundFreqs, indep. cplx comps. ',masterList1,fundFreqs,[],[],[],[],[],A); + +%% RCA +[RCAAxx,W,A,D] = mrC.SpatialFilters.RCA(EEGAxx1{1},'do_whitening',do_whitening); +MASDEEG_rca_comp = mean(RCAAxx.Amp,3); +mrC.Simulate.PlotEEG(MASDEEG_rca_comp,freq,[],'RCA on all freqs ',masterList1,fundFreqs,[],[],[],[],[],A); From a65c98a208a728511ce2209fb7b7650d0b2bdcb1 Mon Sep 17 00:00:00 2001 From: Sebastian Bosse Date: Mon, 17 Sep 2018 21:29:03 +0200 Subject: [PATCH 18/62] preallocation of noise array --- +mrC/+Simulate/SimulateProject.m | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/+mrC/+Simulate/SimulateProject.m b/+mrC/+Simulate/SimulateProject.m index f086d88..6e22237 100644 --- a/+mrC/+Simulate/SimulateProject.m +++ b/+mrC/+Simulate/SimulateProject.m @@ -31,6 +31,12 @@ % signalFF: a seedNum x 1 vector: determines the fundamental % frequencis of sources % nTrials: Number of trials. Noise is redrawn for each trial. + % + % originsource If true, the original source signal will be + % returned in the output, otherwise it will be + % empty. Be careful about using it, if too many + % trials are requested, you might run out of + % memory. % (ROI Parameters) % rois a cell array of roi structure that can be @@ -147,7 +153,8 @@ 'plotting' , 0 ,... 'Save' ,true,... 'cndNum' ,1, ... - 'nTrials' ,1 ... + 'nTrials' ,1, ... + 'originsource' ,false... ); % Roi Type, the names should be according to folders in (svdnl/anatomy/...) @@ -322,21 +329,28 @@ % ----- Generate noise----- % this noise is NS x srcNum matrix, where srcNum is the number of source points on the cortical meshe - noise_signal = zeros(NS, size(spat_dists,1), opt.nTrials); +% tic +% noise_signal = mrC.Simulate.GenerateNoise_2(opt.signalsf, NS, size(spat_dists,1), Noise.mu, AlphaSrc, noise_mixing_data,Noise.spatial_normalization_type,opt.nTrials); +% toc + noise_signal = zeros(NS, size(spat_dists,1), opt.nTrials); for trial_id =1:opt.nTrials % this could be solved more elegantly in GenerateNoise as well.. - [thisNoiseSignal, pink_noise,~, alpha_noise] = mrC.Simulate.GenerateNoise(opt.signalsf, NS, size(spat_dists,1), Noise.mu, AlphaSrc, noise_mixing_data,Noise.spatial_normalization_type); - noiseSignal(:,:,trial_id) = thisNoiseSignal ; + disp(['Trail # ' num2str(trial_id)]) + [thisNoiseSignal] = mrC.Simulate.GenerateNoise(opt.signalsf, NS, size(spat_dists,1), Noise.mu, AlphaSrc, noise_mixing_data,Noise.spatial_normalization_type); + noise_signal(:,:,trial_id) = thisNoiseSignal ; end - %visualizeNoise(noiseSignal, spat_dists, surfData,opt.signalsf) % Just to visualize noise on the cortical surface - %visualizeNoise(alpha_noise, spat_dists, surfData,opt.signalsf) - % %------------------------ADD THE SIGNAL IN THE ROIs-------------------------- disp('Generating EEG signal ...'); subInd = strcmp(cellfun(@(x) x.subID,opt.rois,'UniformOutput',false),subIDs{s}); - [EEGData{s},sourceDataOrigin{s}] = mrC.Simulate.SrcSigMtx(opt.rois{find(subInd)},fwdMatrix,surfData,opt.signalArray,noiseSignal,Noise.lambda,'active_nodes',opt.roiSize,opt.roiSpatfunc);%Noise.spatial_normalization_type);% ROIsig % NoiseParams + [EEGData{s},sourceData] = mrC.Simulate.SrcSigMtx(opt.rois{find(subInd)},fwdMatrix,surfData,opt.signalArray,noise_signal,Noise.lambda,'active_nodes',opt.roiSize,opt.roiSpatfunc);%Noise.spatial_normalization_type);% ROIsig % NoiseParams + if (opt.nTrials==1) || (opt.originsource) % this is to avoid memory problem + sourceDataOrigin{s} = sourceData; + else + sourceDataOrigin{s} = []; + end + %visualizeSource(sourceDataOrigin{s}, surfData,opt.signalsf,0) %% convert EEG to axx format if strcmp(opt.signalType,'SSVEP') From 4084996879d2148f1bee7b28f5162bf17778b398 Mon Sep 17 00:00:00 2001 From: Sebastian Bosse Date: Mon, 17 Sep 2018 21:32:15 +0200 Subject: [PATCH 19/62] removed debug output --- +mrC/+SpatialFilters/CSP.m | 1 - +mrC/+SpatialFilters/RCA.m | 1 - +mrC/+SpatialFilters/SSD.m | 1 - 3 files changed, 3 deletions(-) diff --git a/+mrC/+SpatialFilters/CSP.m b/+mrC/+SpatialFilters/CSP.m index 3b553e6..b6274cb 100644 --- a/+mrC/+SpatialFilters/CSP.m +++ b/+mrC/+SpatialFilters/CSP.m @@ -58,7 +58,6 @@ end if opt.do_whitening % and deflate matrix dimensionality - disp('whitening') [V, D] = eig(sum(C,3)); [ev, desc_idxs] = sort(diag(D), 'descend'); V = V(:,desc_idxs); diff --git a/+mrC/+SpatialFilters/RCA.m b/+mrC/+SpatialFilters/RCA.m index b9bd73d..ca2a3c7 100644 --- a/+mrC/+SpatialFilters/RCA.m +++ b/+mrC/+SpatialFilters/RCA.m @@ -81,7 +81,6 @@ C_xy = real(C_xy); if opt.do_whitening % and deflate matrix dimensionality - disp('whitening') [V, D] = eig(C_xy+C_xx+C_yy); [ev, desc_idxs] = sort(diag(D), 'descend'); V = V(:,desc_idxs); diff --git a/+mrC/+SpatialFilters/SSD.m b/+mrC/+SpatialFilters/SSD.m index fb03431..eb17c76 100644 --- a/+mrC/+SpatialFilters/SSD.m +++ b/+mrC/+SpatialFilters/SSD.m @@ -52,7 +52,6 @@ if opt.do_whitening % and deflate matrix dimensionality - disp('whitening') [V, D] = eig(C_s+C_n); [ev, desc_idxs] = sort(diag(D), 'descend'); V = V(:,desc_idxs); From d1346b7466eb2bc85e2d0ef05666affed0764ec3 Mon Sep 17 00:00:00 2001 From: Sebastian Bosse Date: Mon, 17 Sep 2018 21:36:00 +0200 Subject: [PATCH 20/62] added keyboard control for component visualization --- +mrC/+Simulate/PlotEEG.m | 141 +++++++++++++++++++++++++++------------ 1 file changed, 99 insertions(+), 42 deletions(-) diff --git a/+mrC/+Simulate/PlotEEG.m b/+mrC/+Simulate/PlotEEG.m index e070694..16c1f57 100644 --- a/+mrC/+Simulate/PlotEEG.m +++ b/+mrC/+Simulate/PlotEEG.m @@ -1,4 +1,4 @@ -function h = PlotEEG(ASDEEG,Freq,savepath,subID, masterList,signalFF,SignalType,jcolors,Mode,EOI,FOI,A) +function h = PlotEEG(ASDEEG,Freq,savepath,subID, masterList,signalFF,SignalType,jcolors,Mode,EOI,FOI,A,W) % This function provides a dual plot of electrode amplitude/phase spectrum and topographic % map of amplitude/phase at a specific frequency (similar to powerDiva) @@ -24,9 +24,11 @@ % FOI: the frequency to plot the (initial) spectrum topo map -% Written by ELham Barzegaran,3.7.2018 -% Latest modification: 6.6.2018 +% Written by Elham Barzegaran,3.7.2018 +% modified by EB: 6.6.2018 % modified by sebastian bosse 8.8.2018 +% modified by EB: 8.21.2018 +% modified by sb: 9.17.2018 %% set parameters if ~exist('SignalType','var')|| isempty(SignalType) % plot phase or amp @@ -69,6 +71,8 @@ space = 'comp'; EOI = 1 ; % start with the first component A = abs(A) ; % visualization in amplitude domain + %Mode = ''; + weight = 1; end %% Plot prepration @@ -104,12 +108,33 @@ end set(gca,'tag','info'); + +%% draw buttons for component selection +if strcmpi(space,'comp') + ax1 = axes('Units','pixel','Position',[30 400 50 10],'Visible','off'); + axes(ax1); + text(0, 0,'RC number','fontweight','bold') + popup = uicontrol('Style', 'popup',... + 'String', '1|2|3|4|5|6|7|8|9',... + 'Position', [20 340 100 50],... + 'Callback',@set_component); + % plot which weight + ax2 = axes('Units','pixel','Position',[30 360 50 10],'Visible','off'); + axes(ax2); + text(0, 0,'Weight','fontweight','bold') + popup = uicontrol('Style', 'popup',... + 'String', 'A|W',... + 'Position', [20 300 100 50],... + 'Callback',@set_weight); +end + + %% Loop over plots N = 1; I = 1; while(I) - if strcmp(SignalType,'Amplitude') + if strcmpi(SignalType,'Amplitude') if strcmpi(space,'comp') colorbarLimits = [-0 max(A(:,EOI)*ASDEEG(FOI,EOI)')]; else @@ -117,49 +142,22 @@ else colorbarLimits = [-0 max(ASDEEG(:))]; end end - elseif strcmp(SignalType,'Phase') + elseif strcmpi(SignalType,'Phase') colorbarLimits = [min(ASDEEG(:)) max(ASDEEG(:))]; end - - if exist('sp1','var'),delete(sp1);end % topography map plot - sp1 = subplot(1,2,1); - if strcmpi(Mode,'Interact') && ~strcmpi(space,'comp'), - mrC.plotOnEgi(ASDEEG(FOI,:)',colorbarLimits,false,EOI,false,Probs); - else - if strcmpi(space,'comp') - mrC.plotOnEgi(A(:,EOI)*ASDEEG(FOI,EOI)',colorbarLimits,false,[],false,Probs); - else - mrC.plotOnEgi(ASDEEG(FOI,:)',colorbarLimits,false,[],false,Probs); - end - end - title(['Frequency = ' num2str(Freq(FOI)) 'Hz'],'fontsize',FS); - set(sp1,'tag',num2str(1)); - colormap(conMap); - colorbar; - - if exist('sp2','var'),delete(sp2);end % spectrum plot - sp2 = subplot(2,2,4); - bar(Freq(1:Fmax), ASDEEG(1:Fmax,EOI),.15); - xlim([Freq(1) Freq(Fmax)]); - xlabel('Frequency(Hz)','fontsize',FS-2); - if strcmp(SignalType,'Amplitude') - ylim([0 max(ASDEEG(1:Fmax,EOI))*1.1]); - ylabel('ASD','fontsize',FS-2); - elseif strcmp(SignalType,'Phase') - ylim([min(ASDEEG(1:Fmax,EOI))*1.1 max(ASDEEG(1:Fmax,EOI))*1.1]); - ylabel('Phase(degree)','fontsize',FS-2); - end - set(sp2,'tag',num2str(2)); - hold on; - bar(Freq(FOI), ASDEEG(FOI,EOI),.4,'FaceColor','g','EdgeColor','g'); - - % draw buttons for channel selection + + + %---------------------------HEAD PLOT---------------------------------- + plot_topog(); + %---------------------------SPECTRUM PLOT------------------------------ + plot_spectrum(); + %---------------------------------------------------------------------- if strcmpi(space,'comp') title(['Component ' num2str(num2str(EOI))],'fontsize',FS); else title(['Electrode ' num2str(num2str(EOI))],'fontsize',FS); end - %% Reads keyboard or mouse click + %% ----------------Reads keyboard or mouse click----------------------- if strcmpi(Mode,'Interact') w = waitforbuttonpress; switch w @@ -225,6 +223,7 @@ y = mousept(1,2); % update location switch SPI + case '1' if ~strcmpi(space,'comp') Epos2= repmat([x y],[128 1]); @@ -233,12 +232,70 @@ end case '2' [~,FOI] = min(abs(repmat(x,[1 size(ASDEEG,1)])-Freq)); + end end - end else I = 0; end end -%print(fullfile(savepath,['SimEEG_Subject' subID 'Electrode' num2str(EOI) '_Freq' num2str(Freq(FOI)) 'Hz_' SignalType '.tif']),'-dtiff','-r300');% Later I can update this to contain the simulation parameters + %---------------------------------------------------------------------- + function set_component(source,event) + EOI = get(source,'value'); + plot_topog(); + plot_spectrum(); + end + + function set_weight(source,event) + weight = get(source,'value'); + plot_topog(); + end +%-------------------------------------------------------------------------- + function plot_topog() + % topography map plot + + sp1 = subplot(1,2,1);delete(sp1); + sp1 = subplot(1,2,1); + if strcmpi(Mode,'Interact') && ~strcmpi(space,'comp'), + mrC.plotOnEgi(ASDEEG(FOI,:)',colorbarLimits,false,EOI,false,Probs); + else + if strcmpi(space,'comp') + if weight==1 + conMap = jmaColors('hotcortex'); + mrC.plotOnEgi(A(:,EOI)*ASDEEG(FOI,EOI)',colorbarLimits,false,[],false,Probs); + + else + mrC.plotOnEgi(W(:,EOI),[-max(abs(W(:,EOI))) max(abs(W(:,EOI)))],false,[],false,Probs); + conMap = jmaColors('coolhotcortex'); + end + else + mrC.plotOnEgi(ASDEEG(FOI,:)',colorbarLimits,false,[],false,Probs); + end + end + title(['Frequency = ' num2str(Freq(FOI)) 'Hz'],'fontsize',FS); + set(sp1,'tag',num2str(1)); + colormap(conMap); + colorbar; + end +%-------------------------------------------------------------------------- + function plot_spectrum() + % spectrum plot + SPEC = ASDEEG(1:Fmax,EOI); + sp2 = subplot(2,2,4); delete(sp2); + sp2 = subplot(2,2,4); + bar(Freq(1:Fmax), SPEC,.15); + xlim([Freq(1) Freq(Fmax)]); + xlabel('Frequency(Hz)','fontsize',FS-2); + if strcmp(SignalType,'Amplitude') + ylim([min(SPEC) max(SPEC)*1.1]); + ylabel('ASD','fontsize',FS-2); + elseif strcmp(SignalType,'Phase') + ylim([min(SPEC)*1.1 max(SPEC)*1.1]); + ylabel('Phase(degree)','fontsize',FS-2); + end + set(sp2,'tag',num2str(2)); + + if strcmpi(Mode ,'Interact'),hold on; bar(Freq(FOI), SPEC(FOI),.4,'FaceColor','g','EdgeColor','g'); end + + end end \ No newline at end of file From 362dd2104538dcabdaca9f4369da73b401b39317 Mon Sep 17 00:00:00 2001 From: Sebastian Bosse Date: Mon, 17 Sep 2018 21:36:42 +0200 Subject: [PATCH 21/62] added W to calls of PlotEEG --- .../simulate_example_with_multiple_trials.m | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/Examples/simulate_example_with_multiple_trials.m b/Examples/simulate_example_with_multiple_trials.m index 43ec26d..c70447b 100644 --- a/Examples/simulate_example_with_multiple_trials.m +++ b/Examples/simulate_example_with_multiple_trials.m @@ -63,14 +63,14 @@ Rois1 = cellfun(@(x) x.searchROIs('V2d','wang','R'),RoiList,'UniformOutput',false);% % wang ROI Rois2 = cellfun(@(x) x.searchROIs('V3d','wang','L'),RoiList,'UniformOutput',false);% % wang ROI RoisI = cellfun(@(x,y) x.mergROIs(y),Rois1,Rois2,'UniformOutput',false); -[EEGData1,EEGAxx1,~,masterList1,subIDs1] = mrC.Simulate.SimulateProject(ProjectPath,'anatomyPath',AnatomyPath,'signalArray',outSignal,'signalFF',FundFreq,'signalsf',SF,'NoiseParams',Noise,'rois',RoisI,'Save',true,'cndNum',1,'nTrials',100); +[EEGData1,EEGAxx1,~,masterList1,subIDs1] = mrC.Simulate.SimulateProject(ProjectPath,'anatomyPath',AnatomyPath,'signalArray',outSignal,'signalFF',FundFreq,'signalsf',SF,'NoiseParams',Noise,'rois',RoisI,'Save',true,'cndNum',1,'nTrials',10); close all; - +%% %--------------------------Cond2: V1d_L, V2d_L----------------------------- Rois1 = cellfun(@(x) x.searchROIs('V1d','wang','L'),RoiList,'UniformOutput',false);% % wang ROI Rois2 = cellfun(@(x) x.searchROIs('V2d','wang','L'),RoiList,'UniformOutput',false);% % wang ROI RoisI = cellfun(@(x,y) x.mergROIs(y),Rois1,Rois2,'UniformOutput',false); -[EEGData2,EEGAxx2,~,masterList2,subIDs2] = mrC.Simulate.SimulateProject(ProjectPath,'anatomyPath',AnatomyPath,'signalArray',outSignal,'signalFF',FundFreq,'signalsf',SF,'NoiseParams',Noise,'rois',RoisI,'Save',true,'cndNum',2,'nTrials',100); +[EEGData2,EEGAxx2,~,masterList2,subIDs2] = mrC.Simulate.SimulateProject(ProjectPath,'anatomyPath',AnatomyPath,'signalArray',outSignal,'signalFF',FundFreq,'signalsf',SF,'NoiseParams',Noise,'rois',RoisI,'Save',true,'cndNum',2,'nTrials',10); close all; %% @@ -91,52 +91,51 @@ %% PCA on full freq range [PCAAxx,W,A,D] = mrC.SpatialFilters.PCA(EEGAxx1{1},'freq_range',freq); MASDEEG_pca_comp = mean(PCAAxx.Amp,3); -mrC.Simulate.PlotEEG(MASDEEG_pca_comp,freq,[],'PCA on full freq. range ',masterList1,fundFreqs,[],[],[],[],[],A); +mrC.Simulate.PlotEEG(MASDEEG_pca_comp,freq,[],'PCA on full freq. range ',masterList1,fundFreqs,[],[],[],[],[],A,W); %% PCA on 1st stim freq [PCAAxx,W,A,D] = mrC.SpatialFilters.PCA(EEGAxx1{1},'freq_range',fundFreqs(1)); MASDEEG_pca_comp = mean(PCAAxx.Amp,3); -mrC.Simulate.PlotEEG(MASDEEG_pca_comp,freq,[], sprintf('PCA on %0.1f Hz', fundFreqs(1)), masterList1,fundFreqs,[],[],[],[],[],A); +mrC.Simulate.PlotEEG(MASDEEG_pca_comp,freq,[], sprintf('PCA on %0.1f Hz', fundFreqs(1)), masterList1,fundFreqs,[],[],[],[],[],A,W); %% PCA on 2st stim freq [PCAAxx,W,A,D] = mrC.SpatialFilters.PCA(EEGAxx1{1},'freq_range',fundFreqs(2)); MASDEEG_pca_comp = mean(PCAAxx.Amp,3); -mrC.Simulate.PlotEEG(MASDEEG_pca_comp,freq,[], sprintf('PCA on %0.1f Hz', fundFreqs(2)), masterList1,fundFreqs,[],[],[],[],[],A); +mrC.Simulate.PlotEEG(MASDEEG_pca_comp,freq,[], sprintf('PCA on %0.1f Hz', fundFreqs(2)), masterList1,fundFreqs,[],[],[],[],[],A,W); %% SSD on 1st stim freq [SSDAxx,W,A,D] = mrC.SpatialFilters.SSD(EEGAxx1{1},fundFreqs(1),'do_whitening',true); MASDEEG_ssd_comp = mean(SSDAxx.Amp,3); -mrC.Simulate.PlotEEG(MASDEEG_ssd_comp,freq,[],sprintf('SSD on %0.1f Hz', fundFreqs(1)),masterList1,fundFreqs,[],[],[],[],[],A); +mrC.Simulate.PlotEEG(MASDEEG_ssd_comp,freq,[],sprintf('SSD on %0.1f Hz', fundFreqs(1)),masterList1,fundFreqs,[],[],[],[],[],A,W); %% SSD on 2nd stim freq [SSDAxx,W,A,D] = mrC.SpatialFilters.SSD(EEGAxx1{1},fundFreqs(2),'do_whitening',true); MASDEEG_ssd_comp = mean(SSDAxx.Amp,3); -mrC.Simulate.PlotEEG(MASDEEG_ssd_comp,freq,[],sprintf('SSD on %0.1f Hz', fundFreqs(2)),masterList1,fundFreqs,[],[],[],[],[],A); - +mrC.Simulate.PlotEEG(MASDEEG_ssd_comp,freq,[],sprintf('SSD on %0.1f Hz', fundFreqs(2)),masterList1,fundFreqs,[],[],[],[],[],A,W); %% CSP [CSPAxx,W,A,D] = mrC.SpatialFilters.CSP({EEGAxx1{1},EEGAxx2{1}},'freq_range',[2,3],'do_whitening',do_whitening); class_vis = 1; MASDEEG_csp_comp = mean(CSPAxx{class_vis}.Amp,3); -mrC.Simulate.PlotEEG(MASDEEG_csp_comp,freq,[],'CSP on fundFreqs ',masterList1,fundFreqs,[],[],[],[],[],A); +mrC.Simulate.PlotEEG(MASDEEG_csp_comp,freq,[],'CSP on fundFreqs ',masterList1,fundFreqs,[],[],[],[],[],A,W); %% CSP [CSPAxx,W,A,D] = mrC.SpatialFilters.CSP({EEGAxx1{1},EEGAxx2{1}},'do_whitening',do_whitening); class_vis = 1; MASDEEG_csp_comp = mean(CSPAxx{class_vis}.Amp,3); -mrC.Simulate.PlotEEG(MASDEEG_csp_comp,freq,[],'CSP on all freqs ',masterList1,fundFreqs,[],[],[],[],[],A); +mrC.Simulate.PlotEEG(MASDEEG_csp_comp,freq,[],'CSP on all freqs ',masterList1,fundFreqs,[],[],[],[],[],A,W); %% RCA [RCAAxx,W,A,D] = mrC.SpatialFilters.RCA(EEGAxx1{1},'freq_range',[2,3],'do_whitening',do_whitening); MASDEEG_rca_comp = mean(RCAAxx.Amp,3); -mrC.Simulate.PlotEEG(MASDEEG_rca_comp,freq,[],'RCA on fundFreqs ',masterList1,fundFreqs,[],[],[],[],[],A); +mrC.Simulate.PlotEEG(MASDEEG_rca_comp,freq,[],'RCA on fundFreqs ',masterList1,fundFreqs,[],[],[],[],[],A,W); %% RCA [RCAAxx,W,A,D] = mrC.SpatialFilters.RCA(EEGAxx1{1},'freq_range',[2,3],'do_whitening',do_whitening, 'model_type','cartesian'); MASDEEG_rca_comp = mean(RCAAxx.Amp,3); -mrC.Simulate.PlotEEG(MASDEEG_rca_comp,freq,[],'RCA on fundFreqs, indep. cplx comps. ',masterList1,fundFreqs,[],[],[],[],[],A); +mrC.Simulate.PlotEEG(MASDEEG_rca_comp,freq,[],'RCA on fundFreqs, indep. cplx comps. ',masterList1,fundFreqs,[],[],[],[],[],A,W); %% RCA [RCAAxx,W,A,D] = mrC.SpatialFilters.RCA(EEGAxx1{1},'do_whitening',do_whitening); MASDEEG_rca_comp = mean(RCAAxx.Amp,3); -mrC.Simulate.PlotEEG(MASDEEG_rca_comp,freq,[],'RCA on all freqs ',masterList1,fundFreqs,[],[],[],[],[],A); +mrC.Simulate.PlotEEG(MASDEEG_rca_comp,freq,[],'RCA on all freqs ',masterList1,fundFreqs,[],[],[],[],[],A,W); From 16f3c2cbe49f7d7d4c8b954f07257d788818e4bb Mon Sep 17 00:00:00 2001 From: Sebastian Bosse Date: Mon, 24 Sep 2018 16:54:56 +0200 Subject: [PATCH 22/62] Amplitude of the source signal should not change over trials --- +mrC/+Simulate/SrcSigMtx.m | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/+mrC/+Simulate/SrcSigMtx.m b/+mrC/+Simulate/SrcSigMtx.m index cacec20..fcfc96f 100644 --- a/+mrC/+Simulate/SrcSigMtx.m +++ b/+mrC/+Simulate/SrcSigMtx.m @@ -79,7 +79,7 @@ % Normalize the source signal if strcmp(spatial_normalization_type,'active_nodes') - n_active_nodes_signal = squeeze(sum(sum(abs(sourceTemp))~=0) ) ; + n_active_nodes_signal = squeeze(sum(sum(abs(sourceTemp))~=0) ) ; sourceTemp = n_active_nodes_signal * sourceTemp/norm(sourceTemp,'fro') ; elseif strcmp(spatial_normalization_type,'all_nodes') sourceTemp = sourceTemp/norm(sourceTemp,'fro') ; @@ -90,10 +90,10 @@ sourceTemp = zeros(size(noise)); end -if ndims(noise) == 3 - % add a signal (amplitude) variation between trials - sourceTemp = repmat(sourceTemp,[1,1,size(noise,3)]).* permute(repmat(1-randn(size(noise,3),1)/5,[1 size(noise,1) size(noise,2)]),[2 3 1]);%%%%%% what should be the variance? -end +% if ndims(noise) == 3 +% % add a signal (amplitude) variation between trials +% sourceTemp = repmat(sourceTemp,[1,1,size(noise,3)]).* permute(repmat(1-randn(size(noise,3),1)/5,[1 size(noise,1) size(noise,2)]),[2 3 1]);%%%%%% what should be the variance? +% end % Adds noise to source signal pow = 1; From 8b8c9c926574a727fca468414fbb4f16101959bb Mon Sep 17 00:00:00 2001 From: Sebastian Bosse Date: Wed, 26 Sep 2018 16:59:07 +0200 Subject: [PATCH 23/62] added comment --- +mrC/+Simulate/GenerateMixingData.m | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/+mrC/+Simulate/GenerateMixingData.m b/+mrC/+Simulate/GenerateMixingData.m index 951142d..7dce262 100644 --- a/+mrC/+Simulate/GenerateMixingData.m +++ b/+mrC/+Simulate/GenerateMixingData.m @@ -2,6 +2,10 @@ % Syntax: noise_mixing_data = GenerateMixingData(spat_dists) % Description: This function get the spatial distance between sources and loads the % spatial decay model for coherenc and generates the mixing matrix for noise +% see 10.1121/1.2987429. +% We use the Cholesky decomposition due to complexity considerations. This +% is appropriate as all sources have (approx.) identical PSD and no +% 'perceptual effects' are expected %% -------------------------------------------------------------------------- From 7f4fb3a8253bd92d35bb7fe62482644d7927a533 Mon Sep 17 00:00:00 2001 From: Sebastian Bosse Date: Fri, 12 Oct 2018 17:14:00 +0200 Subject: [PATCH 24/62] bugfix --- +mrC/+Simulate/GenerateNoise.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/+mrC/+Simulate/GenerateNoise.m b/+mrC/+Simulate/GenerateNoise.m index 8042571..78b3f4e 100644 --- a/+mrC/+Simulate/GenerateNoise.m +++ b/+mrC/+Simulate/GenerateNoise.m @@ -46,8 +46,8 @@ if strcmp(noise_mixing_data.mixing_type,'coh') % just in case we want to add other mixing mechanisms % force noise to be spatially coherent within 'hard' frequency % ranges - % for details see: DOI: 10.1121/1.2987429 - f = ifft([-0.5:1/n_samples:0.5-1/n_samples]*f_sampling); % frequncy range + % for details see: DOI:10.1121/1.2987429 + f = fftshift([-0.5:1/n_samples:0.5-1/n_samples]*f_sampling); % frequncy range pink_noise_spec = fft(pink_noise,[],1); for band_idx = 1:length(noise_mixing_data.band_freqs) From bdcf290f71a89dc88af8f35d66f5f50d10589e6c Mon Sep 17 00:00:00 2001 From: Sebastian Bosse Date: Mon, 5 Nov 2018 14:42:00 +0100 Subject: [PATCH 25/62] also return noise-free projection of the simulated signal --- +mrC/+Simulate/SrcSigMtx.m | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/+mrC/+Simulate/SrcSigMtx.m b/+mrC/+Simulate/SrcSigMtx.m index fcfc96f..55a6795 100644 --- a/+mrC/+Simulate/SrcSigMtx.m +++ b/+mrC/+Simulate/SrcSigMtx.m @@ -1,4 +1,4 @@ -function [EEGData,sourceData,roiSet] = SrcSigMtx(rois,fwdMatrix,surfData,signalArray,noise,lambda,spatial_normalization_type,RoiSize,funcType)% ROIsig %NoiseParams +function [EEGData,EEGData_signal, sourceData,roiSet] = SrcSigMtx(rois,fwdMatrix,surfData,signalArray,noise,lambda,spatial_normalization_type,RoiSize,funcType)% ROIsig %NoiseParams % Description: Generate Seed Signal within specified ROIs % @@ -25,6 +25,9 @@ % % OUTPUT: % EEGData: ns x ne matrix: simulated EEG signal + % + % EEGData_signal: fwd projection of the signal only (without noise + % components) % % sourceData: ns x nsrc Matrix: simulated signal in source space % @@ -111,9 +114,10 @@ sourceData = sourceData/norm(sourceData,'fro');% signal and noise are correlated randomly (not on average!). dirty hack: normalize sum % Generate EEG data by multiplication to forward matrix EEGData = sourceData*fwdMatrix'; - end +EEGData_signal = sourceTemp*fwdMatrix'; + % there should be another step: add measurement noise to EEG? end From be1c7883af93b9e3edda6e90074f7ae4676ce047 Mon Sep 17 00:00:00 2001 From: Sebastian Bosse Date: Mon, 5 Nov 2018 14:46:44 +0100 Subject: [PATCH 26/62] return projection of noise-free simulated signal --- +mrC/+Simulate/SimulateProject.m | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/+mrC/+Simulate/SimulateProject.m b/+mrC/+Simulate/SimulateProject.m index 6e22237..7e4038b 100644 --- a/+mrC/+Simulate/SimulateProject.m +++ b/+mrC/+Simulate/SimulateProject.m @@ -1,4 +1,4 @@ -function [EEGData,EEGAxx,sourceDataOrigin,masterList,subIDs] = SimulateProject(projectPath,varargin) +function [EEGData,EEGAxx,EEGData_signal,EEGAxx_signal,sourceDataOrigin,masterList,subIDs] = SimulateProject(projectPath,varargin) % Syntax: [EEGData,EEGAxx,sourceDataOrigin,masterList,subIDs] = SimulateProject(projectPath,varargin) % Description: This function gets the path for a mrc project and simulate @@ -116,6 +116,17 @@ % subject's simulated EEG. This output is % available if the signal type is SSVEP % + % EEGData_signal: a NS x e matrix, containing simulated EEG + % signal without the noise components, + % where NSs is number of time samples and e is the + % number of the electrodes + % + % + % EEGAxx_signal: A cell array containing Axx structure of each + % subject's simulated EEG signal without the + % noise components. This output is + % available if the signal type is SSVEP + % % sourceDataOrigin: a NS x srcNum matrix, containing simulated % EEG in source space before converting to % sensor space EEG, where srcNum is the @@ -314,16 +325,19 @@ load(fullfile(anatDir,subIDs{s},'Standard','meshes','defaultCortex.mat')); surfData = msh.data; surfData.VertexLR = msh.nVertexLR; clear msh; - spat_dists = mrC.Simulate.CalculateSourceDistance(surfData,Noise.distanceType); + % -----This part calculate mixing matrix for coherent noise----- if strcmp(Noise.mixing_type_pink_noise,'coh') mixDir = fullfile(anatDir,subIDs{s},'Standard','meshes',['noise_mixing_data_' Noise.distanceType '.mat']); + spat_dists = mrC.Simulate.CalculateSourceDistance(surfData,Noise.distanceType); if ~exist(mixDir,'file')% if the mixing data is not calculated already + disp(['Calculating mixing matrix for coherent pink noise ...']) noise_mixing_data = mrC.Simulate.GenerateMixingData(spat_dists); - save(mixDir,'noise_mixing_data'); + save(mixDir,'noise_mixing_data','-v7.3'); else - load(mixDir); + disp(['Reading mixing matrix for coherent pink noise ...']) + load(mixDir,'noise_mixing_data'); end end @@ -333,8 +347,8 @@ % noise_signal = mrC.Simulate.GenerateNoise_2(opt.signalsf, NS, size(spat_dists,1), Noise.mu, AlphaSrc, noise_mixing_data,Noise.spatial_normalization_type,opt.nTrials); % toc noise_signal = zeros(NS, size(spat_dists,1), opt.nTrials); - for trial_id =1:opt.nTrials % this could be solved more elegantly in GenerateNoise as well.. - disp(['Trail # ' num2str(trial_id)]) + for trial_id =1:opt.nTrials + disp(['Trial # ' num2str(trial_id)]) [thisNoiseSignal] = mrC.Simulate.GenerateNoise(opt.signalsf, NS, size(spat_dists,1), Noise.mu, AlphaSrc, noise_mixing_data,Noise.spatial_normalization_type); noise_signal(:,:,trial_id) = thisNoiseSignal ; end @@ -343,7 +357,7 @@ disp('Generating EEG signal ...'); subInd = strcmp(cellfun(@(x) x.subID,opt.rois,'UniformOutput',false),subIDs{s}); - [EEGData{s},sourceData] = mrC.Simulate.SrcSigMtx(opt.rois{find(subInd)},fwdMatrix,surfData,opt.signalArray,noise_signal,Noise.lambda,'active_nodes',opt.roiSize,opt.roiSpatfunc);%Noise.spatial_normalization_type);% ROIsig % NoiseParams + [EEGData{s},EEGData_signal{s},sourceData] = mrC.Simulate.SrcSigMtx(opt.rois{find(subInd)},fwdMatrix,surfData,opt.signalArray,noise_signal,Noise.lambda,'active_nodes',opt.roiSize,opt.roiSpatfunc);%Noise.spatial_normalization_type);% ROIsig % NoiseParams if (opt.nTrials==1) || (opt.originsource) % this is to avoid memory problem sourceDataOrigin{s} = sourceData; @@ -355,6 +369,7 @@ %% convert EEG to axx format if strcmp(opt.signalType,'SSVEP') EEGAxx{s}= mrC.Simulate.CreateAxx(EEGData{s},opt);% Converts the simulated signal to Axx format + EEGAxx_signal{s}= mrC.Simulate.CreateAxx(EEGData_signal{s},opt);% Converts the simulated signal to Axx format end %% write output to file From 481f308c574e3584464e6f6dc9c30234be26a5b4 Mon Sep 17 00:00:00 2001 From: Sebastian Bosse Date: Mon, 5 Nov 2018 15:25:02 +0100 Subject: [PATCH 27/62] minor bugfixes --- +mrC/+Simulate/GenerateNoise.m | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/+mrC/+Simulate/GenerateNoise.m b/+mrC/+Simulate/GenerateNoise.m index 78b3f4e..d14bad4 100644 --- a/+mrC/+Simulate/GenerateNoise.m +++ b/+mrC/+Simulate/GenerateNoise.m @@ -40,24 +40,29 @@ %% -----------------------------generate pink noise------------------------ - pink_noise = GetPinkNoise(n_samples, n_nodes );pink_noise_uncoh = pink_noise; - + pink_noise = GetPinkNoise(n_samples, n_nodes ); % impose coherence on pink noise if strcmp(noise_mixing_data.mixing_type,'coh') % just in case we want to add other mixing mechanisms % force noise to be spatially coherent within 'hard' frequency % ranges % for details see: DOI:10.1121/1.2987429 f = fftshift([-0.5:1/n_samples:0.5-1/n_samples]*f_sampling); % frequncy range - + pink_noise_spec = fft(pink_noise,[],1); for band_idx = 1:length(noise_mixing_data.band_freqs) % calc coherence for band C = noise_mixing_data.matrices{band_idx}; - freq_bin_idxs = (noise_mixing_data.band_freqs{band_idx}(1) normalize + % source node-wise variance! + pink_noise = pink_noise./sqrt(mean(abs(pink_noise).^2)); + % TODO: test for coherence else error('%s is not implemented as a mixing method',noise_mixing_data.mixing_type) end @@ -72,7 +77,8 @@ end %% --------------------combine different types of noise-------------------- noise = sqrt(mu/(1+mu))*pink_noise + sqrt(1/(1+mu))*alpha_noise ; - noise = noise/norm(noise,'fro') ;% pink noise and alpha noise are correlated randomly. dirty hack: normalize sum + % TODO: add sensor noise as AWGN + noise = noise/norm(noise,'fro') ;% pink noise and alpha noise are uncorrelated randomly. dirty hack: normalize sum %% ---------------------------show resulting noise------------------------- if false % just to take a look at the noise components f = [-0.5:1/n_samples:0.5-1/n_samples]*f_sampling; % frequncy range @@ -117,7 +123,8 @@ % ensure zero mean value y = y - repmat(mean(y),[n_samples,1]) ; - +%normalize to unit variance +y = y./sqrt(mean(abs(y).^2)); end function pink_noise = GetPinkNoise(n_samples,n_nodes) @@ -133,5 +140,6 @@ noise_spec = fft(randn(M,n_nodes)).*scalings' ; pink_noise = real(ifft(noise_spec)) ; pink_noise = pink_noise(1:n_samples,:) ; + pink_noise = pink_noise./sqrt(mean(abs(pink_noise).^2)) ; end From a486a8cc0538f0ad419a3e129143065c1fa90515 Mon Sep 17 00:00:00 2001 From: Sebastian Bosse Date: Mon, 5 Nov 2018 15:26:14 +0100 Subject: [PATCH 28/62] added svd-based decomposition for increased numerical stability and to obtain less sparse mixing matrix --- +mrC/+Simulate/GenerateMixingData.m | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/+mrC/+Simulate/GenerateMixingData.m b/+mrC/+Simulate/GenerateMixingData.m index 7dce262..de3a34d 100644 --- a/+mrC/+Simulate/GenerateMixingData.m +++ b/+mrC/+Simulate/GenerateMixingData.m @@ -1,4 +1,4 @@ -function noise_mixing_data = GenerateMixingData(spat_dists) +function noise_mixing_data = GenerateMixingData(spat_dists, decomp_algo) % Syntax: noise_mixing_data = GenerateMixingData(spat_dists) % Description: This function get the spatial distance between sources and loads the % spatial decay model for coherenc and generates the mixing matrix for noise @@ -9,6 +9,11 @@ %% -------------------------------------------------------------------------- +if ~exist('decomp_algo','var') || isempty(decomp_algo) + decomp_algo = 'cholesky'; +end + + % preparing mixing matrices load('spatial_decay_models_coherence')% this is located in simulate/private folder, it can be obtained by run the code 'spatial_decay_of_coherence.m' @@ -19,15 +24,29 @@ noise_mixing_data.band_freqs = band_freqs ; noise_mixing_data.mixing_type = 'coh' ; + % we are assuming isolation between hemispheres: calculate mixing + % matrices seperately + hWait = waitbar(0,'Calculating mixing matrices ... '); for freq_band_idx = 1:length(band_freqs) this_spatial_decay_model = best_model.(band_names{freq_band_idx}) ; + mixing_matrix = zeros(size(spat_dists,1),size(spat_dists,2)) ; + + this_coh = this_spatial_decay_model.fun(this_spatial_decay_model.model_params,spat_dists); this_coh = min(max(this_coh,0),1) ; - mixing_data = chol(this_coh) ; % this matrix should become sparse, to be saved - - noise_mixing_data.matrices{freq_band_idx} = sparse(mixing_data .* (mixing_data>0.01)); %make the matrix sparse for saving + + if strcmpi(decomp_algo,'cholesky') + this_mixing_matrix = chol(this_coh) ; + elseif strcmpi(decomp_algo,'eigenvalue') + [V,D] =eig(this_coh); + this_mixing_matrix = sqrt(D)*V' ; + else + error('decomposition method not implemented') + end + + noise_mixing_data.matrices{freq_band_idx} = this_mixing_matrix; waitbar(freq_band_idx/length(band_freqs)); end close(hWait); From e561cd265f87e3a3ced63d9bc04d711e46d98ed2 Mon Sep 17 00:00:00 2001 From: Sebastian Bosse Date: Mon, 5 Nov 2018 15:38:12 +0100 Subject: [PATCH 29/62] speedup by hemisphere-wise coherency of pink noise (assuming no coherency of pink noise between hemispheres) --- +mrC/+Simulate/GenerateMixingData.m | 30 +++++++++++++++-------------- +mrC/+Simulate/GenerateNoise.m | 7 ++++--- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/+mrC/+Simulate/GenerateMixingData.m b/+mrC/+Simulate/GenerateMixingData.m index de3a34d..ba3bc60 100644 --- a/+mrC/+Simulate/GenerateMixingData.m +++ b/+mrC/+Simulate/GenerateMixingData.m @@ -31,22 +31,24 @@ for freq_band_idx = 1:length(band_freqs) this_spatial_decay_model = best_model.(band_names{freq_band_idx}) ; - mixing_matrix = zeros(size(spat_dists,1),size(spat_dists,2)) ; - - - this_coh = this_spatial_decay_model.fun(this_spatial_decay_model.model_params,spat_dists); - this_coh = min(max(this_coh,0),1) ; + mixing_matrix = zeros(size(spat_dists,1),size(spat_dists,2)/2) ; + for hemisphere_idx = 1:2 + this_spat_dists = spat_dists((hemisphere_idx-1)*size(spat_dists,1)/2+(1:size(spat_dists,1)/2),... + (hemisphere_idx-1)*size(spat_dists,2)/2+(1:size(spat_dists,2)/2)); + this_coh = this_spatial_decay_model.fun(this_spatial_decay_model.model_params,this_spat_dists); + this_coh = min(max(this_coh,0),1) ; - if strcmpi(decomp_algo,'cholesky') - this_mixing_matrix = chol(this_coh) ; - elseif strcmpi(decomp_algo,'eigenvalue') - [V,D] =eig(this_coh); - this_mixing_matrix = sqrt(D)*V' ; - else - error('decomposition method not implemented') + if strcmpi(decomp_algo,'cholesky') + this_mixing_matrix = chol(this_coh) ; + elseif strcmpi(decomp_algo,'eigenvalue') + [V,D] =eig(this_coh); + this_mixing_matrix = sqrt(D)*V' ; + else + error('decomposition method not implemented') + end + mixing_matrix((hemisphere_idx-1)*size(spat_dists,1)/2+(1:size(spat_dists,1)/2),:) = this_mixing_matrix; end - - noise_mixing_data.matrices{freq_band_idx} = this_mixing_matrix; + noise_mixing_data.matrices{freq_band_idx} = mixing_matrix; waitbar(freq_band_idx/length(band_freqs)); end close(hWait); diff --git a/+mrC/+Simulate/GenerateNoise.m b/+mrC/+Simulate/GenerateNoise.m index d14bad4..1190d57 100644 --- a/+mrC/+Simulate/GenerateNoise.m +++ b/+mrC/+Simulate/GenerateNoise.m @@ -53,9 +53,10 @@ % calc coherence for band C = noise_mixing_data.matrices{band_idx}; freq_bin_idxs = (noise_mixing_data.band_freqs{band_idx}(1)<=abs(f))&(abs(f) Date: Wed, 7 Nov 2018 13:45:05 +0100 Subject: [PATCH 30/62] enable multiple interactive plots --- +mrC/+Simulate/PlotEEG.m | 222 ++++++++++++++++++++------------------- 1 file changed, 116 insertions(+), 106 deletions(-) diff --git a/+mrC/+Simulate/PlotEEG.m b/+mrC/+Simulate/PlotEEG.m index 16c1f57..e631a9f 100644 --- a/+mrC/+Simulate/PlotEEG.m +++ b/+mrC/+Simulate/PlotEEG.m @@ -127,119 +127,26 @@ 'Position', [20 300 100 50],... 'Callback',@set_weight); end - - -%% Loop over plots - N = 1; -I = 1; -while(I) - if strcmpi(SignalType,'Amplitude') - if strcmpi(space,'comp') - colorbarLimits = [-0 max(A(:,EOI)*ASDEEG(FOI,EOI)')]; - else - if N == 1, colorbarLimits = [-0 max(ASDEEG(FOI,:))]; - else colorbarLimits = [-0 max(ASDEEG(:))]; - end - end - elseif strcmpi(SignalType,'Phase') - colorbarLimits = [min(ASDEEG(:)) max(ASDEEG(:))]; - end +colorbarLimits = [] ; - %---------------------------HEAD PLOT---------------------------------- - plot_topog(); - %---------------------------SPECTRUM PLOT------------------------------ - plot_spectrum(); - %---------------------------------------------------------------------- - if strcmpi(space,'comp') - title(['Component ' num2str(num2str(EOI))],'fontsize',FS); - else - title(['Electrode ' num2str(num2str(EOI))],'fontsize',FS); - end - %% ----------------Reads keyboard or mouse click----------------------- - if strcmpi(Mode,'Interact') - w = waitforbuttonpress; - switch w - case 1 % keyboard - key = get(h,'currentcharacter'); - if key==27 % (Esc key) -> close the plot and return - close; - h=[]; - return - elseif key==13 % (Enter key) -> save the current figure - set(h, 'PaperPosition',[1 1 12 5]); - print(fullfile(savepath,['SimEEG_Subject' subID 'Electrode' num2str(EOI) '_Freq' num2str(Freq(FOI)) 'Hz.tif']),'-dtiff','-r300');% Later I can update this to contain the simulation parameters - elseif strcmp(key,'n')||strcmp(key,'N') % if n or N is pressed -> change head plot normalization - if N==1, N=0; else N=1;end - - elseif key==28 && strcmpi(space,'comp') % cursor left - EOI = mod(EOI-1,size(A,2)+1) ; - if EOI==0 % 0 is not valid - EOI = mod(EOI-1,size(A,2)+1) ; - end - elseif key==29 && strcmpi(space,'comp') % cursor right - EOI = mod(EOI+1,size(A,2)+1) ; - if EOI==0 % 0 is not valid - EOI = mod(EOI+1,size(A,2)+1) ; - end - elseif key==30 && strcmpi(space,'comp') % cursor up - EOI = mod(EOI+10,size(A,2)+1) ; - if EOI==0 % 0 is not valid - EOI = mod(EOI+1,size(A,2)+1) ; - end - elseif key==31 && strcmpi(space,'comp') % cursor down - EOI = mod(EOI-10,size(A,2)+1) ; - if EOI==0 % 0 is not valid - EOI = mod(EOI-1,size(A,2)+1) ; - end - elseif key=='a' - FOI = mod(FOI-1,Fmax+1) ; - if FOI==0 % 0 is not valid - FOI = mod(FOI-1,Fmax+1) ; - end - elseif key=='d' - FOI = mod(FOI+1,Fmax+1) ; - if FOI==0 % 0 is not valid - FOI = mod(FOI+1,Fmax+1) ; - end - elseif key=='w' - FOI = mod(FOI+10,Fmax+1) ; - if FOI==0 % 0 is not valid - FOI = mod(FOI+1,Fmax+1) ; - end - elseif key=='s' - FOI = mod(FOI-10,Fmax+1) ; - if FOI==0 % 0 is not valid - FOI = mod(FOI-1,Fmax+1) ; - end - end - case 0 % mouse click - - mousept = get(gca,'currentPoint'); - SPI = get(gca,'tag'); - x = mousept(1,1); - y = mousept(1,2); - % update location - switch SPI +setColorbar() +%---------------------------HEAD PLOT---------------------------------- +plot_topog(); +%---------------------------SPECTRUM PLOT------------------------------ +plot_spectrum(); +%---------------------------------------------------------------------- - case '1' - if ~strcmpi(space,'comp') - Epos2= repmat([x y],[128 1]); - dis = sqrt(sum((tEpos-Epos2).^2,2)); - [~,EOI] = min(dis); - end - case '2' - [~,FOI] = min(abs(repmat(x,[1 size(ASDEEG,1)])-Freq)); - end - end - else - I = 0; - end +%% ----------------Reads keyboard or mouse click----------------------- +if strcmpi(Mode,'Interact') + set(h, 'WindowKeyPressFcn',@keyPressCallback); + set(h, 'WindowButtonDownFcn', @buttonPressCallback); end - %---------------------------------------------------------------------- + +%---------------------------------------------------------------------- function set_component(source,event) EOI = get(source,'value'); plot_topog(); @@ -250,6 +157,104 @@ function set_weight(source,event) weight = get(source,'value'); plot_topog(); end + + function keyPressCallback(source, eventdata) + key = eventdata.Key; + if key==27 % (Esc key) -> close the plot and return + close; + h=[]; + return + elseif key==13 % (Enter key) -> save the current figure + set(h, 'PaperPosition',[1 1 12 5]); + print(fullfile(savepath,['SimEEG_Subject' subID 'Electrode' num2str(EOI) '_Freq' num2str(Freq(FOI)) 'Hz.tif']),'-dtiff','-r300');% Later I can update this to contain the simulation parameters + elseif strcmp(key,'n')||strcmp(key,'N') % if n or N is pressed -> change head plot normalization + if N==1, N=0; else N=1;end + + elseif strcmpi(key,'leftarrow') && strcmpi(space,'comp') % cursor left + EOI = mod(EOI-1,size(A,2)+1) ; + if EOI==0 % 0 is not valid + EOI = mod(EOI-1,size(A,2)+1) ; + end + elseif strcmpi(key,'rightarrow') && strcmpi(space,'comp') % cursor right + EOI = mod(EOI+1,size(A,2)+1) ; + if EOI==0 % 0 is not valid + EOI = mod(EOI+1,size(A,2)+1) ; + end + elseif strcmpi(key,'uparrow') && strcmpi(space,'comp') % cursor up + EOI = mod(EOI+10,size(A,2)+1) ; + if EOI==0 % 0 is not valid + EOI = mod(EOI+1,size(A,2)+1) ; + end + elseif strcmpi(key,'downarrow') && strcmpi(space,'comp') % cursor down + EOI = mod(EOI-10,size(A,2)+1) ; + if EOI==0 % 0 is not valid + EOI = mod(EOI-1,size(A,2)+1) ; + end + elseif key=='a' + FOI = mod(FOI-1,Fmax+1) ; + if FOI==0 % 0 is not valid + FOI = mod(FOI-1,Fmax+1) ; + end + elseif key=='d' + FOI = mod(FOI+1,Fmax+1) ; + if FOI==0 % 0 is not valid + FOI = mod(FOI+1,Fmax+1) ; + end + elseif key=='w' + FOI = mod(FOI+10,Fmax+1) ; + if FOI==0 % 0 is not valid + FOI = mod(FOI+1,Fmax+1) ; + end + elseif key=='s' + FOI = mod(FOI-10,Fmax+1) ; + if FOI==0 % 0 is not valid + FOI = mod(FOI-1,Fmax+1) ; + end + end + % update plots + setColorbar(); + plot_topog(); + plot_spectrum(); + end + + function buttonPressCallback(source, eventdata) + mousept = get(gca,'currentPoint'); + SPI = get(gca,'tag'); + x = mousept(1,1); + y = mousept(1,2); + % update location + switch SPI + + case '1' + if ~strcmpi(space,'comp') + Epos2= repmat([x y],[128 1]); + dis = sqrt(sum((tEpos-Epos2).^2,2)); + [~,EOI] = min(dis); + end + case '2' + [~,FOI] = min(abs(repmat(x,[1 size(ASDEEG,1)])-Freq)); + end + % update plots + setColorbar(); + plot_topog(); + plot_spectrum(); + end + +%-------------------------------------------------------------------------- + function setColorbar() + if strcmpi(SignalType,'Amplitude') + if strcmpi(space,'comp') + colorbarLimits = [-0 max(A(:,EOI)*ASDEEG(FOI,EOI)')]; + else + if N == 1, colorbarLimits = [-0 max(ASDEEG(FOI,:))]; + else colorbarLimits = [-0 max(ASDEEG(:))]; + end + end + elseif strcmpi(SignalType,'Phase') + colorbarLimits = [min(ASDEEG(:)) max(ASDEEG(:))]; + end + end + %-------------------------------------------------------------------------- function plot_topog() % topography map plot @@ -297,5 +302,10 @@ function plot_spectrum() if strcmpi(Mode ,'Interact'),hold on; bar(Freq(FOI), SPEC(FOI),.4,'FaceColor','g','EdgeColor','g'); end + if strcmpi(space,'comp') + title(['Component ' num2str(num2str(EOI))],'fontsize',FS); + else + title(['Electrode ' num2str(num2str(EOI))],'fontsize',FS); + end end end \ No newline at end of file From 70cff72bd222f93241c9e4fd2cdef8967ea7d97e Mon Sep 17 00:00:00 2001 From: Sebastian Bosse Date: Tue, 13 Nov 2018 12:12:09 +0100 Subject: [PATCH 31/62] do not calculate spatial distances of sources if not need (i.e. when noise mixing data is read from file) --- +mrC/+Simulate/SimulateProject.m | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/+mrC/+Simulate/SimulateProject.m b/+mrC/+Simulate/SimulateProject.m index 7e4038b..38850f2 100644 --- a/+mrC/+Simulate/SimulateProject.m +++ b/+mrC/+Simulate/SimulateProject.m @@ -330,8 +330,9 @@ % -----This part calculate mixing matrix for coherent noise----- if strcmp(Noise.mixing_type_pink_noise,'coh') mixDir = fullfile(anatDir,subIDs{s},'Standard','meshes',['noise_mixing_data_' Noise.distanceType '.mat']); - spat_dists = mrC.Simulate.CalculateSourceDistance(surfData,Noise.distanceType); + if ~exist(mixDir,'file')% if the mixing data is not calculated already + spat_dists = mrC.Simulate.CalculateSourceDistance(surfData,Noise.distanceType); disp(['Calculating mixing matrix for coherent pink noise ...']) noise_mixing_data = mrC.Simulate.GenerateMixingData(spat_dists); save(mixDir,'noise_mixing_data','-v7.3'); @@ -340,16 +341,16 @@ load(mixDir,'noise_mixing_data'); end end - + nSources = size(noise_mixing_data.matrices{1},1); % ----- Generate noise----- % this noise is NS x srcNum matrix, where srcNum is the number of source points on the cortical meshe % tic % noise_signal = mrC.Simulate.GenerateNoise_2(opt.signalsf, NS, size(spat_dists,1), Noise.mu, AlphaSrc, noise_mixing_data,Noise.spatial_normalization_type,opt.nTrials); % toc - noise_signal = zeros(NS, size(spat_dists,1), opt.nTrials); + noise_signal = zeros(NS, nSources, opt.nTrials); for trial_id =1:opt.nTrials disp(['Trial # ' num2str(trial_id)]) - [thisNoiseSignal] = mrC.Simulate.GenerateNoise(opt.signalsf, NS, size(spat_dists,1), Noise.mu, AlphaSrc, noise_mixing_data,Noise.spatial_normalization_type); + [thisNoiseSignal] = mrC.Simulate.GenerateNoise(opt.signalsf, NS, nSources, Noise.mu, AlphaSrc, noise_mixing_data,Noise.spatial_normalization_type); noise_signal(:,:,trial_id) = thisNoiseSignal ; end %------------------------ADD THE SIGNAL IN THE ROIs-------------------------- From 9acfbb8f7f87d13bdc4b1c35846930c30c12559a Mon Sep 17 00:00:00 2001 From: Sebastian Bosse Date: Wed, 21 Nov 2018 13:54:45 +0100 Subject: [PATCH 32/62] added loadobj and saveobj to axx-class --- +mrC/@axx/axx.m | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/+mrC/@axx/axx.m b/+mrC/@axx/axx.m index 75ad1b1..15e79f7 100644 --- a/+mrC/@axx/axx.m +++ b/+mrC/@axx/axx.m @@ -39,6 +39,26 @@ end methods + function s = saveobj(obj) + s.cndNmb = obj.cndNmb ; + s.nTrl = obj.nTrl ; + s.nT = obj.nT ; + s.nCh = obj.nCh ; + s.dTms = obj.dTms ; + s.dFHz = obj.dFHz ; + s.nFr = obj.nFr ; + s.i1F1 = obj.i1F1 ; + s.i1F2 = obj.i1F2 ; + s.DataUnitStr = obj.DataUnitStr; + s.Amp = obj.Amp ; + s.Cos = obj.Cos ; + s.Sin = obj.Sin ; + s.SpecPValue = obj.SpecPValue ; + s.SpecStdErr = obj.SpecStdErr ; + s.Cov = obj.Cov ; + s.Wave = obj.Wave; + end + function obj = axx(axxStrct,AVR) if nargin<2 AVR=1;% average over trials? @@ -131,4 +151,33 @@ function writetofile(thisAxx,FilePath) end end + + methods(Static) + function obj = loadobj(s) + if isstruct(s) + newObj = mrC.axx() ; + newObj.cndNmb = s.cndNmb ; + newObj.nTrl = s.nTrl ; +% newObj.set.nT(s.nT) ; +% newObj.set.nCh(s.nCh) ; + newObj.dTms = s.dTms ; + newObj.dFHz = s.dFHz ; + newObj.nFr = s.nFr ; + newObj.i1F1 = s.i1F1 ; + newObj.i1F2 = s.i1F2 ; + newObj.DataUnitStr = s.DataUnitStr; + newObj.Amp = s.Amp ; + newObj.Cos = s.Cos ; + newObj.Sin = s.Sin ; + newObj.SpecPValue = s.SpecPValue ; + newObj.SpecStdErr = s.SpecStdErr ; + newObj.Cov = s.Cov ; + newObj.Wave = s.Wave; + obj = newObj ; + else + obj = s ; + end + end + end + end \ No newline at end of file From 2098b3332ef9e15f1b2a47ae34d0f0dfeea14acb Mon Sep 17 00:00:00 2001 From: Sebastian Bosse Date: Wed, 21 Nov 2018 14:02:58 +0100 Subject: [PATCH 33/62] minor change --- +mrC/@axx/axx.m | 39 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/+mrC/@axx/axx.m b/+mrC/@axx/axx.m index 15e79f7..9a1088e 100644 --- a/+mrC/@axx/axx.m +++ b/+mrC/@axx/axx.m @@ -39,26 +39,6 @@ end methods - function s = saveobj(obj) - s.cndNmb = obj.cndNmb ; - s.nTrl = obj.nTrl ; - s.nT = obj.nT ; - s.nCh = obj.nCh ; - s.dTms = obj.dTms ; - s.dFHz = obj.dFHz ; - s.nFr = obj.nFr ; - s.i1F1 = obj.i1F1 ; - s.i1F2 = obj.i1F2 ; - s.DataUnitStr = obj.DataUnitStr; - s.Amp = obj.Amp ; - s.Cos = obj.Cos ; - s.Sin = obj.Sin ; - s.SpecPValue = obj.SpecPValue ; - s.SpecStdErr = obj.SpecStdErr ; - s.Cov = obj.Cov ; - s.Wave = obj.Wave; - end - function obj = axx(axxStrct,AVR) if nargin<2 AVR=1;% average over trials? @@ -149,6 +129,25 @@ function writetofile(thisAxx,FilePath) end save(FilePath,'-struct','Sstruct'); end + function s = saveobj(obj) + s.cndNmb = obj.cndNmb ; + s.nTrl = obj.nTrl ; + s.nT = obj.nT ; + s.nCh = obj.nCh ; + s.dTms = obj.dTms ; + s.dFHz = obj.dFHz ; + s.nFr = obj.nFr ; + s.i1F1 = obj.i1F1 ; + s.i1F2 = obj.i1F2 ; + s.DataUnitStr = obj.DataUnitStr; + s.Amp = obj.Amp ; + s.Cos = obj.Cos ; + s.Sin = obj.Sin ; + s.SpecPValue = obj.SpecPValue ; + s.SpecStdErr = obj.SpecStdErr ; + s.Cov = obj.Cov ; + s.Wave = obj.Wave; + end end From 17c1358eecd6b739890b07dfdf0c1711f32d5392 Mon Sep 17 00:00:00 2001 From: Sebastian Bosse Date: Wed, 21 Nov 2018 14:03:22 +0100 Subject: [PATCH 34/62] added saveobj and loadobj methods to ROIs.m --- +mrC/@ROIs/ROIs.m | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/+mrC/@ROIs/ROIs.m b/+mrC/@ROIs/ROIs.m index 6adacfe..7fcd7d9 100644 --- a/+mrC/@ROIs/ROIs.m +++ b/+mrC/@ROIs/ROIs.m @@ -355,7 +355,24 @@ function saveROIs(obj,anatDir) obj = mrC.ROIs(subID,anatDir); obj.saveROIs(anatDir); end - end + end + function s = saveobj(obj) + s.ROIList = obj.ROIList ; + s.subID = obj.subID ; + end end + + methods(Static) + function obj = loadobj(s) + if isstruct(s) + newObj = mrC.ROIs() ; + newObj.ROIList = s.ROIList ; + newObj.subID = s.subID ; + obj = newObj ; + else + obj = s ; + end + end + end end \ No newline at end of file From d4dcda1193c32c3a1e984452bad82358b868ee47 Mon Sep 17 00:00:00 2001 From: Sebastian Bosse Date: Thu, 22 Nov 2018 16:27:13 +0100 Subject: [PATCH 35/62] correction for loadobj --- +mrC/@ROIs/ROIs.m | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/+mrC/@ROIs/ROIs.m b/+mrC/@ROIs/ROIs.m index 7fcd7d9..692f515 100644 --- a/+mrC/@ROIs/ROIs.m +++ b/+mrC/@ROIs/ROIs.m @@ -19,6 +19,10 @@ methods %-----------------------Initializing------------------------------- function obj = ROIs(subID,anatDir) + if nargin == 0 + % just loading + return + end % gets subject IDs and Anatomy Path and generate ROIs class % with all ROIs available for that subject %%%%%%%%%%%%%%%%%%%%%%%%this function should be later updated From 0f5264a678b18045bdf32d14c115a8b9ecbba3cf Mon Sep 17 00:00:00 2001 From: Sebastian Bosse Date: Thu, 22 Nov 2018 16:31:33 +0100 Subject: [PATCH 36/62] consistent normalization weighted sum of pink, alpha and sensor noise normalization (and weighted sum) in sensor space --- +mrC/+Simulate/GenerateNoise.m | 122 +++++++++++++++++++++++++-------- 1 file changed, 95 insertions(+), 27 deletions(-) diff --git a/+mrC/+Simulate/GenerateNoise.m b/+mrC/+Simulate/GenerateNoise.m index 1190d57..f95b265 100644 --- a/+mrC/+Simulate/GenerateNoise.m +++ b/+mrC/+Simulate/GenerateNoise.m @@ -1,5 +1,5 @@ -function [noise, pink_noise, pink_noise_uncoh, alpha_noise] = GenerateNoise(f_sampling, n_samples, n_nodes, mu, alpha_nodes, noise_mixing_data, spatial_normalization_type) -% Syntax: [noise, pink_noise, pink_noise_uncoh, alpha_noise] = GenerateNoise(f_sampling, n_samples, n_nodes, mu, alpha_nodes, noise_mixing_data, spatial_normalization_type) +function [noise, pink_noise, alpha_noise,sensor_noise] = GenerateNoise(f_sampling, n_samples, n_nodes, NoiseParams, noise_mixing_data, spatial_normalization_type,fwdMatrix) +% Syntax: [noise, pink_noise, pink_noise_uncoh, alpha_noise] = GenerateNoise(f_sampling, n_samples, n_nodes, mu, alpha_nodes, noise_mixing_data, spatial_normalization_type,fwdMatrix) % Desciption: GENERATE_NOISE Returns noise of unit variance as a combination of alpha % activity (bandpass filtered white noise) and spatially coherent pink % noise (spectrally shaped white noise) @@ -15,6 +15,10 @@ % different noises to % 'all_nodes': normalize to total number of nodes % 'active_nodes': normalize only to nodes where a specific noise has any activity + % fwdMatrix: Matrix of forward model. If given, + % noise is return projected to channel + % space. This reduces computational + % complexity in during noise generation % OUTPUT: % noise: returns a matrix of size [n_samples,n_nodes] % pink_noise @@ -25,14 +29,28 @@ %% ---------------------------- generate alpha noise------------------------ % + if exist('fwdMatrix','var')|| ~isempty(fwdMatrix) + doFwdProjection = true ; + else + doFwdProjection = false ; + end + alpha_noise = zeros(n_samples,n_nodes); - alpha_noise(:,alpha_nodes) = repmat(GetAlphaActivity(n_samples,f_sampling,[8,12]),[1,length(alpha_nodes )]); + alpha_noise(:,NoiseParams.alpha_nodes) = repmat(GetAlphaActivity(n_samples,f_sampling,[8,12]),[1,length(NoiseParams.alpha_nodes )]); + + if doFwdProjection + alpha_noise = alpha_noise*fwdMatrix' ; + end if strcmp(spatial_normalization_type,'active_nodes') + if doFwdProjection + warning('spatial normalization with active nodes does not really make sense in channel space!') + end n_active_nodes_alpha = sum(sum(abs(alpha_noise))~=0) ; alpha_noise = n_active_nodes_alpha*alpha_noise/norm(alpha_noise,'fro') ; elseif strcmp(spatial_normalization_type,'all_nodes') alpha_noise = alpha_noise/norm(alpha_noise,'fro') ; + else error('%s is not implemented as spatial normalization method', spatial_normalization_type) end @@ -49,26 +67,52 @@ f = fftshift([-0.5:1/n_samples:0.5-1/n_samples]*f_sampling); % frequncy range pink_noise_spec = fft(pink_noise,[],1); + + if doFwdProjection + pink_noise_spec_coh = zeros(size(pink_noise_spec,1), size(fwdMatrix,1)) ; + else + pink_noise_spec_coh = zeros(size(pink_noise_spec)); + end for band_idx = 1:length(noise_mixing_data.band_freqs) % calc coherence for band - C = noise_mixing_data.matrices{band_idx}; + if doFwdProjection + C = noise_mixing_data.matrices_chanSpace{band_idx}; + else + C = noise_mixing_data.matrices{band_idx}; + end freq_bin_idxs = (noise_mixing_data.band_freqs{band_idx}(1)<=abs(f))&(abs(f) normalize - % source node-wise variance! - pink_noise = pink_noise./sqrt(mean(abs(pink_noise).^2)); - % TODO: test for coherence - else + pink_noise = real(ifft(pink_noise_spec_coh,[],1)); + else error('%s is not implemented as a mixing method',noise_mixing_data.mixing_type) end + if false + close all + n_samples = size(pink_noise_spec_coh) ; + normed_pink_noise_spec_coh = pink_noise_spec_coh/norm(pink_noise_spec_coh,'fro'); + normed_pink_noise_spec = pink_noise_spec*fwdMatrix'; + normed_pink_noise_spec = normed_pink_noise_spec/norm(normed_pink_noise_spec,'fro') ; + loglog(mean(abs(normed_pink_noise_spec(1:n_samples/2,:)).^2,2),'b') + hold on + loglog(mean(abs(normed_pink_noise_spec_coh(1:n_samples/2,:)).^2,2),'r') + + + + end + if strcmp(spatial_normalization_type,'active_nodes') + if doFwdProjection + warning('spatial normalization with active nodes does not really make sense in channel space!') + end n_active_nodes_pink = sum(sum(abs(pink_noise))~=0) ; pink_noise = n_active_nodes_pink * pink_noise/norm(pink_noise,'fro') ; elseif strcmp(spatial_normalization_type,'all_nodes') @@ -76,31 +120,55 @@ else error('%s is not implemented as spatial normalization method', spatial_normalization_type) end + + sensor_noise = rand(n_samples, size(fwdMatrix,1)) ; + sensor_noise = sensor_noise/norm(sensor_noise,'fro'); %% --------------------combine different types of noise-------------------- - noise = sqrt(mu/(1+mu))*pink_noise + sqrt(1/(1+mu))*alpha_noise ; - % TODO: add sensor noise as AWGN - noise = noise/norm(noise,'fro') ;% pink noise and alpha noise are uncorrelated randomly. dirty hack: normalize sum + norm_factor = sqrt(NoiseParams.mu.pink^2+NoiseParams.mu.alpha^2+NoiseParams.mu.sensor^2) ; + noise = NoiseParams.mu.pink/norm_factor*pink_noise + NoiseParams.mu.alpha/norm_factor*alpha_noise + NoiseParams.mu.sensor/norm_factor*sensor_noise ; + noise = noise/norm(noise,'fro') ; %% ---------------------------show resulting noise------------------------- - if false % just to take a look at the noise components + if false % just to take a look at the noise components, averaged over all channels for power spectrum f = [-0.5:1/n_samples:0.5-1/n_samples]*f_sampling; % frequncy range t = [0:n_samples-1]/f_sampling ; - subplot(3,2,1) + subplot(4,3,1) + title('pink noise') plot(t, pink_noise(:,1:50:end));xlim([0 2]); - subplot(3,2,2) - plot(f, abs(fftshift(fft(pink_noise(:,1:50:end)))));xlim([0 max(f)]); + subplot(4,3,2) + plot(f, mean(abs(fftshift(fft(pink_noise))),2) );xlim([0 max(f)]); + subplot(4,3,3) + loglog(f, mean(abs(fftshift(fft(pink_noise))),2) );xlim([0 max(f)]); %ylim([0 .2]); - subplot(3,2,3) + subplot(4,3,4) + title('alpha noise') plot(t, alpha_noise(:,1:50:end));xlim([0 2]); - subplot(3,2,4) - plot(f, abs(fftshift(fft(alpha_noise(:,1:50:end)))));xlim([0 max(f)]); + subplot(4,3,5) + plot(f, mean(abs(fftshift(fft(alpha_noise))),2) );xlim([0 max(f)]); + subplot(4,3,6) + loglog(f, mean(abs(fftshift(fft(alpha_noise))),2) );xlim([0 max(f)]); + + %plot(f, abs(fftshift(fft(alpha_noise(:,1:50:end)))));xlim([0 max(f)]); %ylim([0 .2]); + + + subplot(4,3,7) + title('sensor noise') + plot(t, sensor_noise(:,1:50:end));xlim([0 2]); + subplot(4,3,8) + plot(f, abs(fftshift(fft(sensor_noise(:,1:50:end)))));xlim([0 max(f)]); + subplot(4,3,9) + loglog(f, mean(abs(fftshift(fft(sensor_noise))),2) );xlim([0 max(f)]); - subplot(3,2,5) + + subplot(4,3,10) + title(sprintf('combined noise with [%1.2f, %1.2f, %1.2f]', NoiseParams.mu.pink, NoiseParams.mu.alpha, NoiseParams.mu.sensor)); plot(t, noise(:,1:50:end)); xlim([0 2]); - subplot(3,2,6) - plot(f, abs(fftshift(fft(noise(:,1:50:end)))));xlim([0 max(f)]); - %ylim([0 .2]); + subplot(4,3,11) + plot(f, mean(abs(fftshift(fft(noise))),2) );xlim([0 max(f)]); + subplot(4,3,12) + loglog(f, mean(abs(fftshift(fft(noise))),2) );xlim([0 max(f)]); + end end @@ -141,6 +209,6 @@ noise_spec = fft(randn(M,n_nodes)).*scalings' ; pink_noise = real(ifft(noise_spec)) ; pink_noise = pink_noise(1:n_samples,:) ; - pink_noise = pink_noise./sqrt(mean(abs(pink_noise).^2)) ; + pink_noise = pink_noise./norm(pink_noise,'fro'); end From 14e3cd3611093fa43b620ac7d5127b6642b940a4 Mon Sep 17 00:00:00 2001 From: Sebastian Bosse Date: Thu, 22 Nov 2018 16:32:19 +0100 Subject: [PATCH 37/62] some more checks on consistency of params --- +mrC/+SpatialFilters/CSP.m | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/+mrC/+SpatialFilters/CSP.m b/+mrC/+SpatialFilters/CSP.m index b6274cb..5f7b421 100644 --- a/+mrC/+SpatialFilters/CSP.m +++ b/+mrC/+SpatialFilters/CSP.m @@ -17,6 +17,7 @@ % % Written by Sebastian Bosse, 10.8.2018 + opt = ParseArgs(varargin,... 'do_whitening', true, ... 'rank_ratio', 10^-4, ... @@ -32,6 +33,15 @@ if InAxxs{1}.dFHz~=InAxxs{2}.dFHz error('CSP: Frequency resolution not identical in the two given classes') end +if InAxxs{1}.nT ~= InAxxs{2}.nT + error('number of time points differs between classes') ; +end +if InAxxs{1}.nCh ~= InAxxs{2}.nCh + error('number of channels differs between classes') ; +end +if InAxxs{1}.nTrl ~= InAxxs{2}.nTrl + warning('number of trials differs between classes') ; +end cmplx_signal = cell(length(InAxxs),1) ; From 51d7b2e0ccf38788a81d451b8837d064e96ae046 Mon Sep 17 00:00:00 2001 From: Sebastian Bosse Date: Thu, 22 Nov 2018 16:49:56 +0100 Subject: [PATCH 38/62] adding noise and signal in channel space correction of snr-based weighting MISSING: spectral normalization!!! --- +mrC/+Simulate/SrcSigMtx.m | 59 +++++++++++++++++++++----------------- 1 file changed, 32 insertions(+), 27 deletions(-) diff --git a/+mrC/+Simulate/SrcSigMtx.m b/+mrC/+Simulate/SrcSigMtx.m index 55a6795..12e1ec9 100644 --- a/+mrC/+Simulate/SrcSigMtx.m +++ b/+mrC/+Simulate/SrcSigMtx.m @@ -43,6 +43,7 @@ if ~exist('RoiSize','var'), RoiSize = [];end if ~exist('funcType','var'), funcType = [];end + if isempty(rois),% if roi is empty ofr this subject EEGData=[];sourceData=[];roiSet=[]; warning('No ROI found, no simulated EEG is generated'); @@ -54,6 +55,7 @@ roiChunk(rois.ROIList(r).meshIndices,r)=1; end + if size(roiChunk,2)~= size(signalArray,2) EEGData=[]; sourceData=[]; @@ -75,50 +77,53 @@ % place seed signal array in source space - sourceTemp = zeros(size(noise,1),size(noise,2)); + sourceTemp = zeros(size(noise,1),size(fwdMatrix,2)); for s = 1: size(signalArray,2)% place the signal for each seed sourceTemp = sourceTemp + repmat (signalArray(:,s),[1 size(sourceTemp,2)]).*(repmat(spatfunc(:,s),[1 size(sourceTemp,1)])'); end % Normalize the source signal - if strcmp(spatial_normalization_type,'active_nodes') - n_active_nodes_signal = squeeze(sum(sum(abs(sourceTemp))~=0) ) ; - sourceTemp = n_active_nodes_signal * sourceTemp/norm(sourceTemp,'fro') ; - elseif strcmp(spatial_normalization_type,'all_nodes') - sourceTemp = sourceTemp/norm(sourceTemp,'fro') ; - else - error('%s is not implemented as spatial normalization method', spatial_normalization_type) + if size(sourceTemp,2)==size(noise,2) + if strcmp(spatial_normalization_type,'active_nodes') + n_active_nodes_signal = squeeze(sum(sum(abs(sourceTemp))~=0) ) ; + sourceTemp = n_active_nodes_signal * sourceTemp/norm(sourceTemp,'fro') ; + elseif strcmp(spatial_normalization_type,'all_nodes') + sourceTemp = sourceTemp/norm(sourceTemp,'fro') ; + else + error('%s is not implemented as spatial normalization method', spatial_normalization_type) + end end + %else: we normalize in channel space else sourceTemp = zeros(size(noise)); end -% if ndims(noise) == 3 -% % add a signal (amplitude) variation between trials -% sourceTemp = repmat(sourceTemp,[1,1,size(noise,3)]).* permute(repmat(1-randn(size(noise,3),1)/5,[1 size(noise,1) size(noise,2)]),[2 3 1]);%%%%%% what should be the variance? -% end - % Adds noise to source signal -pow = 1; -sourceData = ((lambda/(lambda+1))^pow)*sourceTemp + ((1/(lambda+1))^pow) *noise; +if size(sourceTemp,2)==size(noise,2) + pow = 1; + sourceData = ((lambda/(lambda+1))^pow)*sourceTemp + ((1/(lambda+1))^pow) *noise; + % TODO: reasonable addition of signal and noise + if ndims(sourceData) == 3 + EEGData = zeros(size(sourceData,1),size(fwdMatrix,1),size(sourceData,3)) ; + for trial_idx = 1:size(sourceData,3) + sourceData(:,:,trial_idx) = sourceData(:,:,trial_idx)/norm(sourceData(:,:,trial_idx),'fro') ;% signal and noise are correlated randomly (not on average!). dirty workaround: normalize sum + % Generate EEG data by multiplication to forward matrix + EEGData(:,:,trial_idx) = sourceData(:,:,trial_idx)*fwdMatrix'; -if ndims(sourceData) == 3 - EEGData = zeros(size(sourceData,1),size(fwdMatrix,1),size(sourceData,3)) ; - for trial_idx = 1:size(sourceData,3) - sourceData(:,:,trial_idx) = sourceData(:,:,trial_idx)/norm(sourceData(:,:,trial_idx),'fro') ;% signal and noise are correlated randomly (not on average!). dirty workaround: normalize sum + end + else + sourceData = sourceData/norm(sourceData,'fro');% signal and noise are correlated randomly (not on average!). dirty hack: normalize sum % Generate EEG data by multiplication to forward matrix - EEGData(:,:,trial_idx) = sourceData(:,:,trial_idx)*fwdMatrix'; - + EEGData = sourceData*fwdMatrix'; end -else - sourceData = sourceData/norm(sourceData,'fro');% signal and noise are correlated randomly (not on average!). dirty hack: normalize sum - % Generate EEG data by multiplication to forward matrix - EEGData = sourceData*fwdMatrix'; end - +% else: we add up in channel space EEGData_signal = sourceTemp*fwdMatrix'; +if size(EEGData_signal,2)==size(noise,2) % add up signal and noise in channel space + EEGData = sqrt(lambda/(lambda+1)) *EEGData_signal + sqrt(1/(lambda+1)) *noise; + sourceData = []; % data in source space is not available +end -% there should be another step: add measurement noise to EEG? end function spatfunc = RoiSpatFunc(roiChunk,surfData,RoiSize,Hem,funcType,plotRoi) From 1fc08b74084f0e668068be16fab20945d543002f Mon Sep 17 00:00:00 2001 From: Sebastian Bosse Date: Thu, 22 Nov 2018 16:50:33 +0100 Subject: [PATCH 39/62] plotting function for simple scalp plots --- +mrC/+Simulate/PlotScalp.m | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 +mrC/+Simulate/PlotScalp.m diff --git a/+mrC/+Simulate/PlotScalp.m b/+mrC/+Simulate/PlotScalp.m new file mode 100644 index 0000000..6f71786 --- /dev/null +++ b/+mrC/+Simulate/PlotScalp.m @@ -0,0 +1,24 @@ +function h = PlotScalp(pattern,title_text) + % simplifed function to just plot scalp pattern + if ~exist('title_text','var') + title_text = [] ; + end + + load(fullfile('Electrodeposition.mat')); tEpos =tEpos.xy;% electrode positions used for plots + + h=figure; + set(h,'units','centimeters') + set(h, 'Position',[1 1 35 16]); + set(h,'PaperPositionMode','manual') + + colorbarLimits = [min(pattern),max(pattern)]; + conMap = jmaColors('coolhotcortex'); + Probs{1} = {'facecolor','none','edgecolor','none','markersize',10,'marker','o','markerfacecolor','g' ,'MarkerEdgeColor','k','LineWidth',.5};% plotting parameters + + mrC.plotOnEgi(pattern,colorbarLimits,false,[],false,Probs); + + colormap(conMap); + colorbar; + if ~isempty(title_text) + title(title_text) + end \ No newline at end of file From cdfc4dacb70e11b780549a99013f6af294a61382 Mon Sep 17 00:00:00 2001 From: Sebastian Bosse Date: Thu, 22 Nov 2018 16:51:32 +0100 Subject: [PATCH 40/62] definitions in channel space --- +mrC/+Simulate/SimulateProject.m | 41 ++++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/+mrC/+Simulate/SimulateProject.m b/+mrC/+Simulate/SimulateProject.m index 38850f2..fa2878d 100644 --- a/+mrC/+Simulate/SimulateProject.m +++ b/+mrC/+Simulate/SimulateProject.m @@ -1,4 +1,4 @@ -function [EEGData,EEGAxx,EEGData_signal,EEGAxx_signal,sourceDataOrigin,masterList,subIDs] = SimulateProject(projectPath,varargin) +function [EEGData,EEGAxx,EEGData_signal,EEGAxx_signal,sourceDataOrigin,masterList,subIDs,allSubjFwdMatrices,allSubjRois] = SimulateProject(projectPath,varargin) % Syntax: [EEGData,EEGAxx,sourceDataOrigin,masterList,subIDs] = SimulateProject(projectPath,varargin) % Description: This function gets the path for a mrc project and simulate @@ -236,7 +236,7 @@ %% ===========================GENERATE EEG signal========================== projectPathfold = projectPath; projectPath = subfolders(projectPath,1); % find subjects in the main folder - +allFwdMatrices = {} ; for s = 1:length(projectPath) %--------------------------READ FORWARD SOLUTION--------------------------- % Read forward @@ -283,6 +283,7 @@ srcStrct = readDefaultSourceSpace(subIDs{s}); % Read source structure from freesurfer fwdMatrix = makeForwardMatrixFromMne(fwdStrct ,srcStrct); % Generate Forward matrix end + allSubjFwdMatrices{s} = fwdMatrix ; % ---------------------------Default ROIs---------------------------------- seedNum = size(opt.signalArray,2); % Number of seed sources @@ -306,7 +307,7 @@ NS = size(opt.signalArray,1); % Number of time samples Noise = opt.NoiseParams; Noisefield = fieldnames(Noise); - + % TODO: update tests if ~any(strcmp(Noisefield, 'mu')),Noise.mu = 1;end % power distribution between alpha noise and pink noise ('noise-to-noise ratio') if ~any(strcmp(Noisefield, 'lambda')),Noise.lambda = 1/NS/2;end % power distribution between signal and 'total noise' (SNR) if ~any(strcmp(Noisefield, 'spatial_normalization_type')),Noise.spatial_normalization_type = 'all_nodes';end% 'active_nodes'/['all_nodes'] @@ -341,24 +342,50 @@ load(mixDir,'noise_mixing_data'); end end + + nSources = size(noise_mixing_data.matrices{1},1); % ----- Generate noise----- % this noise is NS x srcNum matrix, where srcNum is the number of source points on the cortical meshe % tic % noise_signal = mrC.Simulate.GenerateNoise_2(opt.signalsf, NS, size(spat_dists,1), Noise.mu, AlphaSrc, noise_mixing_data,Noise.spatial_normalization_type,opt.nTrials); % toc - noise_signal = zeros(NS, nSources, opt.nTrials); + %noise_signal = zeros(NS, nSources, opt.nTrials); + + % calculate coherence in channel space to reduced computational effort + % in every trial + for band_idx = 1:length(noise_mixing_data.band_freqs) + C = noise_mixing_data.matrices{band_idx}; + + % normalize mixing matrices in order to avoid amplification + % NOTE: this if only valid if the 'input signal' is uncorrelated + % accross sources!!! (which is the case for pink Gaussian noise) + % this shold not be necessary (at least if decomposition is based + % on cholesky) + C = C./repmat(sqrt(sum((C.^2),1)),size(C,1),1); + C_chan = zeros(size(fwdMatrix')); + for hemi = 1:2 % hemisphere by hemisphere + source_idxs = (hemi-1)*size(C,2)+1:hemi*size(C,2) ; + C_chan(source_idxs,:) = C(source_idxs,:)*fwdMatrix(:,source_idxs)'; + end + noise_mixing_data.matrices_chanSpace{band_idx} = C_chan; + end + + noise = zeros(NS, size(fwdMatrix,1), opt.nTrials) ; for trial_id =1:opt.nTrials disp(['Trial # ' num2str(trial_id)]) - [thisNoiseSignal] = mrC.Simulate.GenerateNoise(opt.signalsf, NS, nSources, Noise.mu, AlphaSrc, noise_mixing_data,Noise.spatial_normalization_type); - noise_signal(:,:,trial_id) = thisNoiseSignal ; + [thisNoise,thisPinkNoise,thisAlphaNoise,thisSensorNoise] = mrC.Simulate.GenerateNoise(opt.signalsf, NS, nSources, Noise, noise_mixing_data,Noise.spatial_normalization_type,fwdMatrix); + noise(:,:,trial_id) = thisNoise ; end + + %------------------------ADD THE SIGNAL IN THE ROIs-------------------------- disp('Generating EEG signal ...'); subInd = strcmp(cellfun(@(x) x.subID,opt.rois,'UniformOutput',false),subIDs{s}); - [EEGData{s},EEGData_signal{s},sourceData] = mrC.Simulate.SrcSigMtx(opt.rois{find(subInd)},fwdMatrix,surfData,opt.signalArray,noise_signal,Noise.lambda,'active_nodes',opt.roiSize,opt.roiSpatfunc);%Noise.spatial_normalization_type);% ROIsig % NoiseParams + allSubjRois{s} = opt.rois{find(subInd)} ; + [EEGData{s},EEGData_signal{s},sourceData] = mrC.Simulate.SrcSigMtx(opt.rois{find(subInd)},fwdMatrix,surfData,opt.signalArray,noise,Noise.lambda,'active_nodes',opt.roiSize,opt.roiSpatfunc);%Noise.spatial_normalization_type);% ROIsig % NoiseParams if (opt.nTrials==1) || (opt.originsource) % this is to avoid memory problem sourceDataOrigin{s} = sourceData; From 4438cfd2a5553cf48935ef1f508dc90717f44542 Mon Sep 17 00:00:00 2001 From: Sebastian Bosse Date: Thu, 22 Nov 2018 20:10:41 +0100 Subject: [PATCH 41/62] correction in normalization. Still not considering spectral normalization!!! --- +mrC/+Simulate/SrcSigMtx.m | 1 + 1 file changed, 1 insertion(+) diff --git a/+mrC/+Simulate/SrcSigMtx.m b/+mrC/+Simulate/SrcSigMtx.m index 12e1ec9..d6fa73d 100644 --- a/+mrC/+Simulate/SrcSigMtx.m +++ b/+mrC/+Simulate/SrcSigMtx.m @@ -120,6 +120,7 @@ % else: we add up in channel space EEGData_signal = sourceTemp*fwdMatrix'; if size(EEGData_signal,2)==size(noise,2) % add up signal and noise in channel space + EEGData_signal = EEGData_signal/norm(EEGData_signal,'fro') ; EEGData = sqrt(lambda/(lambda+1)) *EEGData_signal + sqrt(1/(lambda+1)) *noise; sourceData = []; % data in source space is not available end From 3736f21cb2a913a61d661070f423b2eb2b3b6cd6 Mon Sep 17 00:00:00 2001 From: Sebastian Bosse Date: Tue, 11 Dec 2018 10:53:36 +0100 Subject: [PATCH 42/62] simplified indexing of spectral components --- +mrC/+SpatialFilters/CSP.m | 18 +++++++++++++++ +mrC/+SpatialFilters/PCA.m | 32 ++++++++++++++++---------- +mrC/+SpatialFilters/RCA.m | 41 +++++++++++++++++++++------------ +mrC/+SpatialFilters/SSD.m | 47 ++++++++++++++++++++++++++------------ 4 files changed, 97 insertions(+), 41 deletions(-) diff --git a/+mrC/+SpatialFilters/CSP.m b/+mrC/+SpatialFilters/CSP.m index 5f7b421..a311352 100644 --- a/+mrC/+SpatialFilters/CSP.m +++ b/+mrC/+SpatialFilters/CSP.m @@ -84,8 +84,18 @@ for class_idx = 1:length(InAxxs) C_w(:,:,class_idx) = P' * C(:,:,class_idx) * P ; + + if max(max(abs(C(:,:,class_idx)-C(:,:,class_idx)')))>10^-10 + error('CSP: Whitened covariance matrix is not symmetric') + end + C_w(:,:,class_idx) = (C_w(:,:,class_idx)+C_w(:,:,class_idx)')/2 ; end + + + + + if opt.do_whitening % calculation in 'white space' [W,D] = eig(C_w(:,:,1)-C_w(:,:,2)) ; else % calculation in channel space @@ -97,6 +107,11 @@ A = sum(C,3) * W *pinv(W'*sum(C,3)*W); +if max(imag(A(:)))>0 + error('should not be complex!!!') +end + + OutAxxs = InAxxs ; for class_idx = 1:length(InAxxs) % project to csp domain @@ -105,5 +120,8 @@ temp = W'*reshape(permute(InAxxs{class_idx}.Sin,[2,1,3]),size(InAxxs{class_idx}.Sin,2),[]); OutAxxs{class_idx}.Sin = permute(reshape(temp,dims,size(InAxxs{class_idx}.Sin,1),size(InAxxs{class_idx}.Sin,3)),[2,1,3]); OutAxxs{class_idx}.Amp = abs(OutAxxs{class_idx}.Cos +1i *OutAxxs{class_idx}.Sin); + temp = W'*reshape(permute(InAxxs{class_idx}.Wave,[2,1,3]),size(InAxxs{class_idx}.Wave,2),[]); + OutAxxs{class_idx}.Wave = permute(reshape(temp,dims,size(InAxxs{class_idx}.Wave,1),size(InAxxs{class_idx}.Wave,3)),[2,1,3]); + end diff --git a/+mrC/+SpatialFilters/PCA.m b/+mrC/+SpatialFilters/PCA.m index efab8a9..64fb826 100644 --- a/+mrC/+SpatialFilters/PCA.m +++ b/+mrC/+SpatialFilters/PCA.m @@ -17,24 +17,25 @@ % Written by Sebastian Bosse, 3.8.2018 opt = ParseArgs(varargin,... - 'freq_range', InAxx.dFHz*[1:(InAxx.nFr-1)] ... + 'freq_range', InAxx.dFHz*[1:(InAxx.nFr-1)], ... + 'model_type','complex' ... ); -freq_idxs = 1+opt.freq_range/InAxx.dFHz ; % shift as 0Hz has idx 1 +freqs = [0:InAxx.nFr-2,InAxx.nFr-1:-1:1]*InAxx.dFHz; +locs = ismember(freqs,opt.freq_range) ; -if freq_idxs(1)==1 % 0Hz is considered - cmplx_signal = cat(1, InAxx.Cos(freq_idxs(end:-1:2),:,:),InAxx.Cos(freq_idxs,:,:)) ... % even real part - + 1i*cat(1, -InAxx.Sin(freq_idxs(end:-1:2),:,:),InAxx.Sin(freq_idxs,:,:)); % odd imag part - -else - cmplx_signal = cat(1, InAxx.Cos(freq_idxs(end:-1:1),:,:),InAxx.Cos(freq_idxs,:,:)) ... % even real part - + 1i*cat(1, -InAxx.Sin(freq_idxs(end:-1:1),:,:),InAxx.Sin(freq_idxs,:,:)); % odd imag part -end +% build full cmplx spectrum +cmplx_signal = [InAxx.Cos;InAxx.Cos(end-1:-1:2,:,:)] + 1i*[InAxx.Sin;-InAxx.Sin(end-1:-1:2,:,:)]; +% reduce cmplx spectrum according to desired freq_range +cmplx_signal = cmplx_signal(locs,:,:) ; +if strcmpi(opt.model_type,'cartesian') + cmplx_signal = [real(cmplx_signal);imag(cmplx_signal)] ; +end +C =reshape(permute(cmplx_signal,[2,1,3]),size(cmplx_signal,2),[])*(reshape(permute(cmplx_signal,[2,1,3]),size(cmplx_signal,2),[]))'; -C =reshape(permute(cmplx_signal,[2,1,3]),size(cmplx_signal,2),[])*conj(reshape(permute(cmplx_signal,[2,1,3]),size(cmplx_signal,2),[]))'; if sum(abs(imag(C(:))))/sum(abs(real(C(:))))>10^-10 error('PCA: Covariance matrix is complex') @@ -45,16 +46,23 @@ [D,sorted_idx] = sort(diag(D),'descend') ; W = W(:,sorted_idx); A = C * W * pinv(W'*C*W); - +if max(imag(A(:)))>0 + error('should not be complex!!!') +end % project to pca domain OutAxx = InAxx ; temp = W'*reshape(permute(InAxx.Cos,[2,1,3]),size(InAxx.Cos,2),[]); OutAxx.Cos = permute(reshape(temp,size(InAxx.Cos,2),size(InAxx.Cos,1),size(InAxx.Cos,3)),[2,1,3]); + temp = W'*reshape(permute(InAxx.Sin,[2,1,3]),size(InAxx.Sin,2),[]); OutAxx.Sin = permute(reshape(temp,size(InAxx.Sin,2),size(InAxx.Sin,1),size(InAxx.Sin,3)),[2,1,3]); + OutAxx.Amp = abs(OutAxx.Cos +1i *OutAxx.Sin); +temp = W'*reshape(permute(InAxx.Wave,[2,1,3]),size(InAxx.Wave,2),[]); +OutAxx.Wave = permute(reshape(temp,size(InAxx.Wave,2),size(InAxx.Wave,1),size(InAxx.Wave,3)),[2,1,3]); + diff --git a/+mrC/+SpatialFilters/RCA.m b/+mrC/+SpatialFilters/RCA.m index ca2a3c7..b0d0762 100644 --- a/+mrC/+SpatialFilters/RCA.m +++ b/+mrC/+SpatialFilters/RCA.m @@ -26,7 +26,10 @@ 'model_type','complex' ... ); -freq_idxs = 1+opt.freq_range/InAxx.dFHz ; % shift as 0Hz has idx 1 +freqs = [0:InAxx.nFr-2,InAxx.nFr-1:-1:1]*InAxx.dFHz; +locs = ismember(freqs,opt.freq_range) ; + + n_trials = size(InAxx.Cos,3); trial_pair_idxs = combnk(1:n_trials,2) ; @@ -34,31 +37,27 @@ if strcmpi(opt.model_type,'complex') - if freq_idxs(1)==1 % 0Hz is considered - cmplx_signal = cat(1, InAxx.Cos(freq_idxs(2:end),:,:),InAxx.Cos(freq_idxs,:,:)) ... % even real part - + 1i*cat(1, -InAxx.Sin(freq_idxs(2:end),:,:),InAxx.Sin(freq_idxs,:,:)); % odd imag part - else - cmplx_signal = cat(1, InAxx.Cos(freq_idxs,:,:),InAxx.Cos(freq_idxs,:,:)) ... % even real part - + 1i*cat(1, -InAxx.Sin(freq_idxs,:,:),InAxx.Sin(freq_idxs,:,:)); % odd imag part - end + % build full cmplx spectrum + cmplx_signal = [InAxx.Cos;InAxx.Cos(end-1:-1:2,:,:)] + 1i*[InAxx.Sin;-InAxx.Sin(end-1:-1:2,:,:)]; + % reduce cmplx spectrum according to desired freq_range + cmplx_signal = cmplx_signal(locs,:,:) ; % Note that this is different to Jacek's derivation and implementation as % it jointly considers real and imaginary values % Cross-trial correlation - C_xy = conj(reshape(permute(cmplx_signal(:,:,trial_pair_idxs(:,1)),[2,1,3]),size(cmplx_signal,2),[]))*... + C_xy = (reshape(permute(cmplx_signal(:,:,trial_pair_idxs(:,1)),[2,1,3]),size(cmplx_signal,2),[]))*... reshape(permute(cmplx_signal(:,:,trial_pair_idxs(:,2)),[2,1,3]),size(cmplx_signal,2),[])'/... size(trial_pair_idxs(:,1),1) ; % Within-trial correlation - C_xx = conj(reshape(permute(cmplx_signal(:,:,trial_pair_idxs(:,1)),[2,1,3]),size(cmplx_signal,2),[]))*... + C_xx = (reshape(permute(cmplx_signal(:,:,trial_pair_idxs(:,1)),[2,1,3]),size(cmplx_signal,2),[]))*... reshape(permute(cmplx_signal(:,:,trial_pair_idxs(:,1)),[2,1,3]),size(cmplx_signal,2),[])'/... size(trial_pair_idxs(:,1),1) ; - C_yy = conj(reshape(permute(cmplx_signal(:,:,trial_pair_idxs(:,2)),[2,1,3]),size(cmplx_signal,2),[]))*... + C_yy = (reshape(permute(cmplx_signal(:,:,trial_pair_idxs(:,2)),[2,1,3]),size(cmplx_signal,2),[]))*... reshape(permute(cmplx_signal(:,:,trial_pair_idxs(:,2)),[2,1,3]),size(cmplx_signal,2),[])'/... size(trial_pair_idxs(:,1),1) ; elseif strcmpi(opt.model_type,'cartesian') C_xy = reshape(permute(InAxx.Cos(freq_idxs,:,trial_pair_idxs(:,1)),[2,1,3]),size(InAxx.Cos,2),[])*... reshape(permute(InAxx.Cos(freq_idxs,:,trial_pair_idxs(:,2)),[2,1,3]),size(InAxx.Cos,2),[])' ; - C_xx = reshape(permute(InAxx.Cos(freq_idxs,:,trial_pair_idxs(:,1)),[2,1,3]),size(InAxx.Cos,2),[])*... reshape(permute(InAxx.Cos(freq_idxs,:,trial_pair_idxs(:,1)),[2,1,3]),size(InAxx.Cos,2),[])' ; @@ -70,15 +69,23 @@ if sum(abs(imag(C_xx(:))))/sum(abs(real(C_xx(:))))>10^-10 error('RCA: Covariance matrix is complex') end +if max(max(abs(C_xy-C_xy')))>10^-10 + error('RCA: Cross-trial matrix is not symmetric') +end + if sum(abs(imag(C_yy(:))))/sum(abs(real(C_yy(:))))>10^-10 error('RCA: Covariance matrix is complex') end if sum(abs(imag(C_xy(:))))/sum(abs(real(C_xy(:))))>10^-10 error('RCA: Covariance matrix is complex') end + + C_xx = real(C_xx); C_yy = real(C_yy); C_xy = real(C_xy); +C_xy = (C_xy+C_xy')/2; + if opt.do_whitening % and deflate matrix dimensionality [V, D] = eig(C_xy+C_xx+C_yy); @@ -96,7 +103,7 @@ C_yy_w = P' * C_yy * P; C_xy_w = P' * C_xy * P; -[W,D] = eig(C_xy_w,C_xx_w+C_yy_w) ; +[W,D] = eig(C_xy_w,C_xy_w+C_xx_w+C_yy_w) ; [D,sorted_idx] = sort(diag(D),'descend') ; W = W(:,sorted_idx); @@ -109,10 +116,16 @@ A = (C_xx+C_yy) * W * pinv(W'*(C_xx+C_yy)*W); +if max(imag(A(:)))>0 + error('should not be complex!!!') +end + % project to rca domain OutAxx = InAxx ; temp = W'*reshape(permute(InAxx.Cos,[2,1,3]),size(InAxx.Cos,2),[]); OutAxx.Cos = permute(reshape(temp,dims,size(InAxx.Cos,1),size(InAxx.Cos,3)),[2,1,3]); temp = W'*reshape(permute(InAxx.Sin,[2,1,3]),size(InAxx.Sin,2),[]); OutAxx.Sin = permute(reshape(temp,dims,size(InAxx.Sin,1),size(InAxx.Sin,3)),[2,1,3]); -OutAxx.Amp = abs(OutAxx.Cos +1i *OutAxx.Sin); \ No newline at end of file +OutAxx.Amp = abs(OutAxx.Cos +1i *OutAxx.Sin); +temp = W'*reshape(permute(InAxx.Wave,[2,1,3]),size(InAxx.Wave,2),[]); +OutAxx.Wave = permute(reshape(temp,dims,size(InAxx.Wave,1),size(InAxx.Wave,3)),[2,1,3]); diff --git a/+mrC/+SpatialFilters/SSD.m b/+mrC/+SpatialFilters/SSD.m index eb17c76..cdf4bbb 100644 --- a/+mrC/+SpatialFilters/SSD.m +++ b/+mrC/+SpatialFilters/SSD.m @@ -1,4 +1,4 @@ -function [OutAxx,W,A,D] = SSD(InAxx,freqs,varargin) +function [OutAxx,W,A,D] = SSD(InAxx,signal_freqs,varargin) % This function calculates finds a spatial filter based on the the SSD of % InAxx. The signal is assumed in freqs, noise is assumed in the @@ -24,21 +24,17 @@ 'rank_ratio', 10^-4 ... ); -freq_range = 0:InAxx.dFHz:InAxx.dFHz*(InAxx.nFr-1); -freq_idxs = find(ismember(freq_range,freqs)); -real_part_signal = InAxx.Cos(freq_idxs,:,:); -imag_part_signal = InAxx.Sin(freq_idxs,:,:); -cmplx_signal = cat(1, real_part_signal,real_part_signal) ... % even real part - + 1i*cat(1, -imag_part_signal,imag_part_signal); % odd imag part +freqs = [0:InAxx.nFr-2,InAxx.nFr-1:-1:1]*InAxx.dFHz; +signal_locs = find(ismember(freqs,signal_freqs)) ; +noise_locs = reshape(repmat(signal_locs,2,1)+[-1;+1],1,[]); -real_part_noise = cat(1,InAxx.Cos(freq_idxs-1,:,:),InAxx.Cos(freq_idxs+1,:,:)); -imag_part_noise = cat(1,InAxx.Sin(freq_idxs-1,:,:),InAxx.Sin(freq_idxs+1,:,:)); -cmplx_noise = cat(1, real_part_noise,real_part_noise) ... % even real part - + 1i*cat(1, -imag_part_noise,imag_part_noise); % odd imag part - -C_s =conj(reshape(permute(cmplx_signal,[2,1,3]),size(cmplx_signal,2),[]))*(reshape(permute(cmplx_signal,[2,1,3]),size(cmplx_signal,2),[])'); -C_n =conj(reshape(permute(cmplx_noise,[2,1,3]),size(cmplx_noise,2),[]))*(reshape(permute(cmplx_noise,[2,1,3]),size(cmplx_noise,2),[])'); +cmplx_full = [InAxx.Cos;InAxx.Cos(end-1:-1:2,:,:)] + 1i*[InAxx.Sin;-InAxx.Sin(end-1:-1:2,:,:)]; +cmplx_signal = cmplx_full(signal_locs,:,:); +cmplx_noise = cmplx_full(noise_locs,:,:); + +C_s =reshape(permute(cmplx_signal,[2,1,3]),size(cmplx_signal,2),[])*reshape(permute(cmplx_signal,[2,1,3]),size(cmplx_signal,2),[])'; +C_n =reshape(permute(cmplx_noise,[2,1,3]),size(cmplx_noise,2),[])*reshape(permute(cmplx_noise,[2,1,3]),size(cmplx_noise,2),[])'; if sum(abs(imag(C_n(:))))/sum(abs(real(C_n(:))))>10^-10 error('SSD: Covariance matrix is complex') @@ -52,7 +48,7 @@ if opt.do_whitening % and deflate matrix dimensionality - [V, D] = eig(C_s+C_n); + [V, D] = eig(C_s); [ev, desc_idxs] = sort(diag(D), 'descend'); V = V(:,desc_idxs); @@ -66,6 +62,18 @@ C_s_w = P' * C_s * P; C_n_w = P' * C_n * P; + +if max(max(abs(C_s_w-C_s_w')))>10^-10 + error('SSD: Whitened signal covariance matrix is not symmetric') +end + +if max(max(abs(C_n_w-C_n_w')))>10^-10 + error('SSD: Whitened noise covariance matrix is not symmetric') +end + +C_s_w = (C_s_w+C_s_w')/2 ; +C_n_w = (C_n_w+C_n_w')/2 ; + [W,D] =eig(C_s_w,C_n_w); [D,sorted_idx] = sort(diag(D),'descend') ; W = W(:,sorted_idx); @@ -74,14 +82,23 @@ A = C_s * W * pinv(W'*C_s*W); +if max(imag(A(:)))>0 + error('should not be complex!!!') +end + % project to ssd domain OutAxx = InAxx ; + temp = W'*reshape(permute(InAxx.Cos,[2,1,3]),size(InAxx.Cos,2),[]); OutAxx.Cos = permute(reshape(temp,dims,size(InAxx.Cos,1),size(InAxx.Cos,3)),[2,1,3]); + temp = W'*reshape(permute(InAxx.Sin,[2,1,3]),size(InAxx.Sin,2),[]); OutAxx.Sin = permute(reshape(temp,dims,size(InAxx.Sin,1),size(InAxx.Sin,3)),[2,1,3]); + OutAxx.Amp = abs(OutAxx.Cos +1i *OutAxx.Sin); +temp = W'*reshape(permute(InAxx.Wave,[2,1,3]),size(InAxx.Wave,2),[]); +OutAxx.Wave = permute(reshape(temp,dims,size(InAxx.Wave,1),size(InAxx.Wave,3)),[2,1,3]); From 6e91571af512cbd6b95236c0dbbfd170d0f86bc6 Mon Sep 17 00:00:00 2001 From: Sebastian Bosse Date: Tue, 11 Dec 2018 11:09:11 +0100 Subject: [PATCH 43/62] some updates --- +mrC/+Simulate/PlotScalp.m | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/+mrC/+Simulate/PlotScalp.m b/+mrC/+Simulate/PlotScalp.m index 6f71786..287a78c 100644 --- a/+mrC/+Simulate/PlotScalp.m +++ b/+mrC/+Simulate/PlotScalp.m @@ -1,4 +1,4 @@ -function h = PlotScalp(pattern,title_text) +function PlotScalp(pattern,title_text) % simplifed function to just plot scalp pattern if ~exist('title_text','var') title_text = [] ; @@ -6,19 +6,12 @@ load(fullfile('Electrodeposition.mat')); tEpos =tEpos.xy;% electrode positions used for plots - h=figure; - set(h,'units','centimeters') - set(h, 'Position',[1 1 35 16]); - set(h,'PaperPositionMode','manual') - colorbarLimits = [min(pattern),max(pattern)]; conMap = jmaColors('coolhotcortex'); Probs{1} = {'facecolor','none','edgecolor','none','markersize',10,'marker','o','markerfacecolor','g' ,'MarkerEdgeColor','k','LineWidth',.5};% plotting parameters mrC.plotOnEgi(pattern,colorbarLimits,false,[],false,Probs); - colormap(conMap); - colorbar; if ~isempty(title_text) title(title_text) end \ No newline at end of file From 34b1db422f8b349c39f5bc496bbddcbadb75c5ab Mon Sep 17 00:00:00 2001 From: Sebastian Bosse Date: Tue, 11 Dec 2018 13:49:44 +0100 Subject: [PATCH 44/62] added script to test spatial filters --- Examples/test_spatial_filters.m | 251 ++++++++++++++++++++++++++++++++ 1 file changed, 251 insertions(+) create mode 100644 Examples/test_spatial_filters.m diff --git a/Examples/test_spatial_filters.m b/Examples/test_spatial_filters.m new file mode 100644 index 0000000..d5ae6d2 --- /dev/null +++ b/Examples/test_spatial_filters.m @@ -0,0 +1,251 @@ +%% Add latest mrC +clear;clc +mrCFolder = fileparts(fileparts(mfilename('fullpath')));%'/Users/kohler/code/git'; +addpath(genpath(mrCFolder)); +addpath('../../../BrewerMap/') +%% +DestPath = '/export/data/eeg_simulation'; +AnatomyPath = fullfile(DestPath,'anatomy'); +ProjectPath = fullfile(DestPath,'FwdProject2'); + +% Pre-select ROIs +[RoiList,subIDs] = mrC.Simulate.GetRoiClass(ProjectPath,AnatomyPath);% 13 subjects with Wang atlab +Wangs = cellfun(@(x) {x.getAtlasROIs('wang')},RoiList); +Wangnums = cellfun(@(x) x.ROINum,Wangs)>0; + +% define noise properties +Noise.mu.pink=2; +Noise.mu.alpha=2; +Noise.mu.sensor=2; + +% define locations of sources +%--------------------------Cond1: V2d_R ----------------------------- +Rois1 = cellfun(@(x) x.searchROIs('V2d','wang','R'),RoiList,'UniformOutput',false);% % wang ROI +do_new_data_generation = false; +% generate or read from disk +if ~exist('data_for_spatial_filter_test.mat','file') | do_new_data_generation + n_trials = 1000 ; + Noise.lambda = 0 ; % noise only + [outSignal, FundFreq, SF]= mrC.Simulate.ModelSeedSignal('signalType','SSVEP','signalFreq',[2],'signalHarmonic',{[2,0,1.5,0]},'signalPhase',{[.1,0,.2,0]}); + [EEGData_noise,EEGAxx_noise,EEGData_signal,EEGAxx_signal,~,masterList,subIDs,allSubjFwdMatrices,allSubjRois] = mrC.Simulate.SimulateProject(ProjectPath,'anatomyPath',AnatomyPath,'signalArray',outSignal,'signalFF',FundFreq,'signalsf',SF,'NoiseParams',Noise,'rois',Rois1,'Save',false,'cndNum',1,'nTrials',n_trials); + save('data_for_spatial_filter_test.mat') +else + load('data_for_spatial_filter_test.mat') +end +%% +% mix signal and nose according to SNR and convert to Axx +opt.signalFF=FundFreq ; +opt.signalsf=SF ; +opt.cndNum = 1; + +% note that this SNR is defined over the full spectrum, while the signal is +% narrowbanded +SNR = -22; +lambda = 10^(SNR/10) ; +EEGData = {}; +EEGAxx = {} ; +for subj_idx = 1:length(EEGData_signal) + EEGData{subj_idx} = sqrt(lambda/(1+lambda))*EEGData_signal{subj_idx} + sqrt(1/(1+lambda)) * EEGData_noise{subj_idx} ; + EEGAxx{subj_idx} = mrC.Simulate.CreateAxx(EEGData{subj_idx},opt) ; +end + + +%% +% test spatial filters +subj_idx = 1 ; + +fund_freq_idx = 1 ; +numTrials_list = 2.^[1,2,3,4,5,6,7]; +nDraws = 20 ; +n_comps = 1 ; +thisFundFreq = FundFreq(fund_freq_idx) ; + +rois = allSubjRois{subj_idx} ; +fwdMatrix = allSubjFwdMatrices{subj_idx} ; + +source_pattern = zeros(size(fwdMatrix,1),length(rois.ROIList)) ; +for roi_idx = 1:length(rois.ROIList) + % assuming uniform activation + source_pattern(:,roi_idx) = sum(fwdMatrix(:,rois.ROIList(roi_idx).meshIndices ),2) ; +end + +decomp_methods = {'pca','ssd','csp','rca'} ; +considered_harms=[1,2] ; + +for nTrial_idx = 1:length(numTrials_list) + nUsedTrials = numTrials_list(nTrial_idx); + for draw_idx = 1:nDraws + fprintf('nUsedTrials = %i, draw_idx = %i \n',nUsedTrials, draw_idx) ; + random_numbers = randperm(EEGAxx_noise{1}.nTrl) ; + % todo: improve indexing to avoid identical trials in different + % draws. requires simulation of enough trials + % for starters: just take random trials + trial_idxs = random_numbers(1:nUsedTrials); + + thisAxx = EEGAxx{subj_idx}; + thisAxx.nTrl = nUsedTrials; + thisAxx.Amp = thisAxx.Amp(:,:,trial_idxs); + thisAxx.Cos = thisAxx.Cos(:,:,trial_idxs); + thisAxx.Sin = thisAxx.Sin(:,:,trial_idxs); + thisAxx.Wave = thisAxx.Wave(:,:,trial_idxs); + thisTempMean = mean(mean(thisAxx.Wave,3),1) ; + + % make sure the no-stimulation condition does not see the same + % noise component + noise_trial_idxs = random_numbers(nUsedTrials+1:2*nUsedTrials); + thisNoiseAxx = EEGAxx_noise{subj_idx}; + thisNoiseAxx.nTrl = nUsedTrials; + thisNoiseAxx.Amp = thisNoiseAxx.Amp(:,:,noise_trial_idxs); + thisNoiseAxx.Cos = thisNoiseAxx.Cos(:,:,noise_trial_idxs); + thisNoiseAxx.Sin = thisNoiseAxx.Sin(:,:,noise_trial_idxs); + thisNoiseAxx.Wave = thisNoiseAxx.Wave(:,:,noise_trial_idxs); + + for decomp_method_idx = 1:length(decomp_methods) + this_decomp_method = decomp_methods{decomp_method_idx}; + + if strcmpi(this_decomp_method,'pca') + [thisDecompAxx,thisW,thisA,thisD] = mrC.SpatialFilters.PCA(thisAxx,'freq_range',thisFundFreq*considered_harms); + elseif strcmpi(this_decomp_method,'pca_cart') + [thisDecompAxx,thisW,thisA,thisD] = mrC.SpatialFilters.PCA(thisAxx,'freq_range',thisFundFreq*considered_harms,'model_type','cartesian'); + elseif strcmpi(this_decomp_method,'fullfreqPca') + [thisDecompAxx,thisW,thisA,thisD] = mrC.SpatialFilters.PCA(thisAxx); + elseif strcmpi(this_decomp_method,'fullfreqPca') + [thisDecompAxx,thisW,thisA,thisD] = mrC.SpatialFilters.PCA(thisAxx,'freq_range',[1:50]); + elseif strcmpi(this_decomp_method,'tpca') + [thisDecompAxx,thisW,thisA,thisD] = mrC.SpatialFilters.tPCA(thisAxx); + elseif strcmpi(this_decomp_method,'ssd') + [thisDecompAxx,thisW,thisA,thisD]= mrC.SpatialFilters.SSD(thisAxx,thisFundFreq*considered_harms,'do_whitening',true); + elseif strcmpi(this_decomp_method,'rca') + [thisDecompAxx,thisW,thisA,thisD] = mrC.SpatialFilters.RCA(thisAxx,'freq_range',thisFundFreq*considered_harms,'do_whitening',true); + + elseif strcmpi(this_decomp_method,'csp') + [theseDecompAxxs,thisW,thisA,thisD] = mrC.SpatialFilters.CSP({thisAxx,thisNoiseAxx},'freq_range',thisFundFreq*considered_harms,'do_whitening',true); + thisDecompAxx=theseDecompAxxs{1}; + end + for i = 1:size(thisA,2) + if source_pattern(:,1)'*thisA(:,i)<0 + thisA(:,i) = thisA(:,i)*-1 ; + end + end + Axx_compspace.(this_decomp_method){nTrial_idx}{draw_idx} = thisDecompAxx ; + W.(this_decomp_method){nTrial_idx}{draw_idx} = thisW ; + A.(this_decomp_method){nTrial_idx}{draw_idx} = thisA ; + D.(this_decomp_method){nTrial_idx}{draw_idx} = thisD ; + + % metrics for first 2 components + % calculate error angles + freqs = [0:thisDecompAxx.nFr]*thisDecompAxx.dFHz; + signal_freq_idxs = find(ismember(freqs,thisFundFreq*considered_harms)); + noise_freq_idxs = [signal_freq_idxs-1,signal_freq_idxs+1] ; + + for comp_idx =1:min(n_comps,size(thisA,2)) + err_angles.(this_decomp_method)(comp_idx,nTrial_idx,draw_idx) = 180/pi* acos(abs(source_pattern(:,1)'*thisA(:,comp_idx))/sqrt(sum(source_pattern(:,1).^2)*sum(thisA(:,comp_idx).^2))) ; + if abs(imag(err_angles.(this_decomp_method)(comp_idx,nTrial_idx,draw_idx)))>10^-10 + error('angle should not be complex') + else + err_angles.(this_decomp_method)(comp_idx,nTrial_idx,draw_idx)=... + real(err_angles.(this_decomp_method)(comp_idx,nTrial_idx,draw_idx)); + end + %calculate snrs assuming ssveps, mean over all trials + snrs.(this_decomp_method)(comp_idx,nTrial_idx,draw_idx)=mean(2*mean(thisDecompAxx.Amp(signal_freq_idxs,comp_idx,:).^2)./mean(thisDecompAxx.Amp(noise_freq_idxs,comp_idx,:).^2)); + % calculate residuals as mse over samples and trials + % TODO: needs some sort of normalization!! + + est_signal = squeeze(thisDecompAxx.Wave(:,comp_idx,:) ); + ref_signal = squeeze(repmat(EEGAxx_signal{subj_idx}.Wave(:,1,:),1,1,size(thisDecompAxx.Wave,3))) ; + % normalize to equal power before calculating residual + est_signal = est_signal/sqrt(mean(est_signal(:).^2)); + ref_signal = ref_signal/sqrt(mean(ref_signal(:).^2)); + + residuals.(this_decomp_method)(comp_idx,nTrial_idx,draw_idx) =... + min(... + mean(mean((ref_signal-est_signal).^2)),... + mean(mean((ref_signal+est_signal).^2))); + end + end + end +end +%% +% scalp plots +FigH = figure('DefaultAxesPosition', [0.1, 0.1, 0.8, 0.8]); +nCols = 1+n_comps*length(decomp_methods) ; +nRows = length(numTrials_list) ; + +rowCounter = 1 ; + +subplot(nRows,nCols,1+nCols*floor(nRows/2)) +mrC.Simulate.PlotScalp(source_pattern,'OP' ); + +for nTrial_idx = 1:length(numTrials_list) +colCounter = 1 ; + +for decomp_method_idx=1:length(decomp_methods) + this_decomp_method = decomp_methods{decomp_method_idx}; + for comp_idx = 1:min(n_comps, size(A.(this_decomp_method){nTrial_idx}{1},2 )) + subplot_tight(nRows,nCols,1+(colCounter-1)*n_comps+comp_idx+(rowCounter-1)*nCols,[0.04,0.005]) + if rowCounter == 1 + this_title = sprintf('%s %1i, %i trials',this_decomp_method, comp_idx,numTrials_list(nTrial_idx)) ; + else + this_title = sprintf('%i trials',numTrials_list(nTrial_idx)); + + end + mrC.Simulate.PlotScalp(A.(this_decomp_method){nTrial_idx}{1}(:,comp_idx),this_title ); + end + colCounter = colCounter +1 ; +end +rowCounter = rowCounter+1 ; +end + +% plots angular error of topographies +figure +colors = brewermap(4,'Set2') ; +for comp_idx = 1:1 +for decomp_method_idx=1:length(decomp_methods) + this_decomp_method = decomp_methods{decomp_method_idx}; + if comp_idx==1 + plot(numTrials_list,median(err_angles.(this_decomp_method)(comp_idx,:,:),3),'-o','LineWidth',2,'MarkerSize',10,'color',colors(decomp_method_idx,:)); + else + plot(numTrials_list,median(err_angles.(this_decomp_method)(comp_idx,:,:),3),':o','LineWidth',2,'MarkerSize',10,'color',colors(decomp_method_idx,:)); + end + hold on +end +end +[~, hobj, ~, ~] = legend(decomp_methods); +hl = findobj(hobj,'type','line'); +set(hl,'LineWidth',1.5); +ht = findobj(hobj,'type','text'); +set(ht,'FontSize',12); +title(sprintf('err ang vs num trials, comp %i',comp_idx)) +xlabel('number of trials') +ylabel('err ang.') + +% plot snrs +figure +comp_idx =1; +for decomp_method_idx=1:length(decomp_methods) + this_decomp_method = decomp_methods{decomp_method_idx}; + plot(numTrials_list,median(snrs.(this_decomp_method)(comp_idx,:,:),3),'-o','LineWidth',2,'MarkerSize',10); + hold on +end +[~, hobj, ~, ~] = legend(decomp_methods); +hl = findobj(hobj,'type','line'); +set(hl,'LineWidth',1.5); +ht = findobj(hobj,'type','text'); +set(ht,'FontSize',12); +xlabel('number of trials') +ylabel('snr') + +% plot residual +figure +for decomp_method_idx=1:length(decomp_methods) + this_decomp_method = decomp_methods{decomp_method_idx}; + plot(numTrials_list,median(residuals.(this_decomp_method)(comp_idx,:,:),3),'-o','LineWidth',2,'MarkerSize',10); + hold on +end +[~, hobj, ~, ~] = legend(decomp_methods); +hl = findobj(hobj,'type','line'); +set(hl,'LineWidth',1.5); +ht = findobj(hobj,'type','text'); +set(ht,'FontSize',12); +xlabel('number of trials') +ylabel('residuals') From 69de919579ba2c2f8cb5cda19ff310088c9b5e0c Mon Sep 17 00:00:00 2001 From: EBarzegaran Date: Fri, 14 Dec 2018 14:23:00 -0800 Subject: [PATCH 45/62] fix a few things --- +mrC/+Simulate/GenerateNoise.m | 14 +++++---- +mrC/+Simulate/SimulateProject.m | 49 +------------------------------- 2 files changed, 9 insertions(+), 54 deletions(-) diff --git a/+mrC/+Simulate/GenerateNoise.m b/+mrC/+Simulate/GenerateNoise.m index f95b265..53cf0ec 100644 --- a/+mrC/+Simulate/GenerateNoise.m +++ b/+mrC/+Simulate/GenerateNoise.m @@ -29,14 +29,14 @@ %% ---------------------------- generate alpha noise------------------------ % - if exist('fwdMatrix','var')|| ~isempty(fwdMatrix) - doFwdProjection = true ; + if ~exist('fwdMatrix','var')|| isempty(fwdMatrix) + doFwdProjection = false; else - doFwdProjection = false ; + doFwdProjection = true ; end alpha_noise = zeros(n_samples,n_nodes); - alpha_noise(:,NoiseParams.alpha_nodes) = repmat(GetAlphaActivity(n_samples,f_sampling,[8,12]),[1,length(NoiseParams.alpha_nodes )]); + alpha_noise(:,NoiseParams.AlphaSrc) = repmat(GetAlphaActivity(n_samples,f_sampling,[8,12]),[1,length(NoiseParams.AlphaSrc )]); if doFwdProjection alpha_noise = alpha_noise*fwdMatrix' ; @@ -58,7 +58,7 @@ %% -----------------------------generate pink noise------------------------ - pink_noise = GetPinkNoise(n_samples, n_nodes ); + pink_noise = GetPinkNoise(n_samples, n_nodes); % impose coherence on pink noise if strcmp(noise_mixing_data.mixing_type,'coh') % just in case we want to add other mixing mechanisms % force noise to be spatially coherent within 'hard' frequency @@ -82,10 +82,12 @@ end freq_bin_idxs = (noise_mixing_data.band_freqs{band_idx}(1)<=abs(f))&(abs(f) Date: Wed, 19 Dec 2018 12:31:07 +0100 Subject: [PATCH 46/62] added option for no mixing on source level --- +mrC/+Simulate/GenerateNoise.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/+mrC/+Simulate/GenerateNoise.m b/+mrC/+Simulate/GenerateNoise.m index 53cf0ec..fbb1afb 100644 --- a/+mrC/+Simulate/GenerateNoise.m +++ b/+mrC/+Simulate/GenerateNoise.m @@ -93,7 +93,7 @@ end end pink_noise = real(ifft(pink_noise_spec_coh,[],1)); - else + elseif ~strcmp(noise_mixing_data.mixing_type,'none') error('%s is not implemented as a mixing method',noise_mixing_data.mixing_type) end From 61d05de810f23d528dfefac89baf1620ec0b5789 Mon Sep 17 00:00:00 2001 From: bosse Date: Wed, 19 Dec 2018 12:33:11 +0100 Subject: [PATCH 47/62] bugfix --- +mrC/+Simulate/GenerateNoise.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/+mrC/+Simulate/GenerateNoise.m b/+mrC/+Simulate/GenerateNoise.m index fbb1afb..955d842 100644 --- a/+mrC/+Simulate/GenerateNoise.m +++ b/+mrC/+Simulate/GenerateNoise.m @@ -123,7 +123,7 @@ error('%s is not implemented as spatial normalization method', spatial_normalization_type) end - sensor_noise = rand(n_samples, size(fwdMatrix,1)) ; + sensor_noise = randn(n_samples, size(fwdMatrix,1)) ; sensor_noise = sensor_noise/norm(sensor_noise,'fro'); %% --------------------combine different types of noise-------------------- norm_factor = sqrt(NoiseParams.mu.pink^2+NoiseParams.mu.alpha^2+NoiseParams.mu.sensor^2) ; From 44f8b602a0582f5690268bbd65b243516f2e75b6 Mon Sep 17 00:00:00 2001 From: bosse Date: Wed, 19 Dec 2018 22:01:12 +0100 Subject: [PATCH 48/62] bugfix --- +mrC/+Simulate/GenerateNoise.m | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/+mrC/+Simulate/GenerateNoise.m b/+mrC/+Simulate/GenerateNoise.m index 955d842..a587484 100644 --- a/+mrC/+Simulate/GenerateNoise.m +++ b/+mrC/+Simulate/GenerateNoise.m @@ -192,8 +192,7 @@ [b,a] = butter(3, freq_band/sampling_freq*2); y = filter(b,a, x); -% ensure zero mean value -y = y - repmat(mean(y),[n_samples,1]) ; + %normalize to unit variance y = y./sqrt(mean(abs(y).^2)); end From 381ac926f9fbd2db52020c5c610ddb023c69c2df Mon Sep 17 00:00:00 2001 From: sebobosse Date: Thu, 20 Dec 2018 12:34:15 +0100 Subject: [PATCH 49/62] added plotting --- .../private/SpatialDecayofCoherence.m | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/+mrC/+Simulate/private/SpatialDecayofCoherence.m b/+mrC/+Simulate/private/SpatialDecayofCoherence.m index be5b7ac..c14a50e 100644 --- a/+mrC/+Simulate/private/SpatialDecayofCoherence.m +++ b/+mrC/+Simulate/private/SpatialDecayofCoherence.m @@ -74,3 +74,53 @@ end legend(handles,freq_band_names) save('spatial_decay_models_coherence','model_fun','model_params','best_model','band_freqs') +%% +% plot best model +if true + close all + + addpath('../../functionPool/BrewerMap/') + set(0,'defaultfigurecolor',[1 1 1]) + + plot_dir = fullfile('/Users/bosse/dev/matlab/mrC/Examples/plots') ; + if ~exist(plot_dir) + mkdir(plot_dir) + end + + sorted_freq_band_names = sort(freq_band_names); + + alw = 0.75; % AxesLineWidth + fsz = 11; % Fontsize + lw = 1.5; % LineWidth + msz = 8; % MarkerSize + + x_temp = 0:0.01:5 ; + colors = brewermap(length(freq_band_names),'Set2'); + fig = figure; + legend_entries = {} ; + + for i = 1:length(sorted_freq_band_names ) + legend_entries{end+1} = sprintf('\\%s-band',sorted_freq_band_names {i}); + plot(x_temp,best_model.(sorted_freq_band_names {i}).fun(... + best_model.(sorted_freq_band_names {i}).model_params ,x_temp),'color',colors(i,:),'linewidth',lw); + hold on + end + xlabel('Spatial Distance [mm]') + ylabel('Coherence') + ylim([0,1]) + xlim([0,4]) + legend(legend_entries,'FontSize',fsz,'location','northeastoutside') +end +set(gca, 'FontSize', fsz, 'LineWidth', alw); %<- Set properties +fig_width = 8.5/2 ; +fig_height = 5/8*fig_width ; +fig.PaperPositionMode = 'manual'; +fig.PaperUnits = 'inches'; +fig.Units = 'inches'; +fig.PaperPosition = [0, 0, fig_width,fig_height]; +fig.PaperSize = [fig_width,fig_height]; +fig.Position = [0.0, 0.0, fig_width, fig_height]; +fig.Resize = 'off'; +fig.InvertHardcopy = 'off'; +filename = fullfile(plot_dir,'spatial_decay_coherence'); +print(filename,'-dpdf'); \ No newline at end of file From 97766afcb6bfce7de7cb6895194b468be9cb368f Mon Sep 17 00:00:00 2001 From: EBarzegaran Date: Thu, 20 Dec 2018 14:35:59 -0800 Subject: [PATCH 50/62] Add two source example for spatial filters --- +mrC/+Simulate/SrcSigMtx.m | 2 +- +mrC/@axx/axx.m | 29 + Examples/test_spatial_filters_2sources.m | 340 +++ tools/BrewerMap/LICENSE.TXT | 176 ++ tools/BrewerMap/README.md | 79 + tools/BrewerMap/brewermap.m | 540 +++++ tools/BrewerMap/brewermap_view.m | 364 ++++ tools/plotting/export_fig/.gitignore | 5 + tools/plotting/export_fig/.ignore/pdftops.txt | 1 - .../plotting/export_fig/ImageSelection.class | Bin 0 -> 1092 bytes tools/plotting/export_fig/ImageSelection.java | 38 + tools/plotting/export_fig/LICENSE | 27 + tools/plotting/export_fig/README.md | 270 +++ tools/plotting/export_fig/append_pdfs.m | 124 ++ tools/plotting/export_fig/copyfig.m | 60 +- tools/plotting/export_fig/crop_borders.m | 157 ++ tools/plotting/export_fig/eps2pdf.m | 246 ++- tools/plotting/export_fig/export_fig.m | 1912 +++++++++++------ tools/plotting/export_fig/fix_lines.m | 73 +- tools/plotting/export_fig/ghostscript.m | 250 ++- tools/plotting/export_fig/im2gif.m | 201 ++ tools/plotting/export_fig/isolate_axes.m | 177 +- tools/plotting/export_fig/license.txt | 24 - tools/plotting/export_fig/pdf2eps.m | 62 +- tools/plotting/export_fig/pdftops.m | 203 +- tools/plotting/export_fig/print2array.m | 327 +-- tools/plotting/export_fig/print2eps.m | 714 ++++-- .../export_fig/read_write_entire_textfile.m | 37 + tools/plotting/export_fig/user_string.m | 134 +- tools/plotting/export_fig/using_hg2.m | 36 + tools/plotting/subplot_tight.m | 99 + 31 files changed, 5272 insertions(+), 1435 deletions(-) create mode 100644 Examples/test_spatial_filters_2sources.m create mode 100644 tools/BrewerMap/LICENSE.TXT create mode 100644 tools/BrewerMap/README.md create mode 100644 tools/BrewerMap/brewermap.m create mode 100644 tools/BrewerMap/brewermap_view.m create mode 100644 tools/plotting/export_fig/.gitignore delete mode 100644 tools/plotting/export_fig/.ignore/pdftops.txt create mode 100644 tools/plotting/export_fig/ImageSelection.class create mode 100644 tools/plotting/export_fig/ImageSelection.java create mode 100644 tools/plotting/export_fig/LICENSE create mode 100644 tools/plotting/export_fig/README.md create mode 100644 tools/plotting/export_fig/append_pdfs.m mode change 100755 => 100644 tools/plotting/export_fig/copyfig.m create mode 100644 tools/plotting/export_fig/crop_borders.m mode change 100755 => 100644 tools/plotting/export_fig/eps2pdf.m mode change 100755 => 100644 tools/plotting/export_fig/export_fig.m mode change 100755 => 100644 tools/plotting/export_fig/fix_lines.m mode change 100755 => 100644 tools/plotting/export_fig/ghostscript.m create mode 100644 tools/plotting/export_fig/im2gif.m mode change 100755 => 100644 tools/plotting/export_fig/isolate_axes.m delete mode 100644 tools/plotting/export_fig/license.txt mode change 100755 => 100644 tools/plotting/export_fig/pdf2eps.m mode change 100755 => 100644 tools/plotting/export_fig/pdftops.m mode change 100755 => 100644 tools/plotting/export_fig/print2array.m mode change 100755 => 100644 tools/plotting/export_fig/print2eps.m create mode 100644 tools/plotting/export_fig/read_write_entire_textfile.m mode change 100755 => 100644 tools/plotting/export_fig/user_string.m create mode 100644 tools/plotting/export_fig/using_hg2.m create mode 100644 tools/plotting/subplot_tight.m diff --git a/+mrC/+Simulate/SrcSigMtx.m b/+mrC/+Simulate/SrcSigMtx.m index d6fa73d..c2ef719 100644 --- a/+mrC/+Simulate/SrcSigMtx.m +++ b/+mrC/+Simulate/SrcSigMtx.m @@ -121,7 +121,7 @@ EEGData_signal = sourceTemp*fwdMatrix'; if size(EEGData_signal,2)==size(noise,2) % add up signal and noise in channel space EEGData_signal = EEGData_signal/norm(EEGData_signal,'fro') ; - EEGData = sqrt(lambda/(lambda+1)) *EEGData_signal + sqrt(1/(lambda+1)) *noise; + EEGData = sqrt(lambda/(lambda+1)) *(EEGData_signal) + sqrt(1/(lambda+1)) *noise; sourceData = []; % data in source space is not available end diff --git a/+mrC/@axx/axx.m b/+mrC/@axx/axx.m index 9a1088e..dc0cc1d 100644 --- a/+mrC/@axx/axx.m +++ b/+mrC/@axx/axx.m @@ -148,6 +148,35 @@ function writetofile(thisAxx,FilePath) s.Cov = obj.Cov ; s.Wave = obj.Wave; end + + function outAxx = SelectTrials(obj,TIdx) + % Select the trials indicated uin TIdx vector. + if max(TIdx)>obj.nTrl || min(TIdx)<=0 + error('Wrong trial indexes'); + end + + obj.nTrl = numel(TIdx); + obj.Amp = obj.Amp(:,:,TIdx); + obj.Cos = obj.Cos(:,:,TIdx); + obj.Sin = obj.Sin(:,:,TIdx); + obj.Wave = obj.Wave(:,:,TIdx); + outAxx = obj; + end + + function outAxx = MergeTrials(obj1,obj2) + % Select the trials indicated uin TIdx vector. + if obj1.nT~=obj2.nT || obj1.nCh~=obj2.nCh || obj1.dTms~=obj2.dTms || obj1.dFHz~=obj2.dFHz + error('Axx classes do not match: Time and frequency features should be the same...'); + end + + outAxx = obj1; + outAxx.nTrl = obj1.nTrl+obj2.nTrl; + outAxx.Amp = cat(3,obj1.Amp,obj2.Amp); + outAxx.Cos = cat(3,obj1.Cos,obj2.Cos); + outAxx.Sin = cat(3,obj1.Cos,obj2.Sin); + outAxx.Wave = cat(3,obj1.Wave,obj2.Wave); + + end end diff --git a/Examples/test_spatial_filters_2sources.m b/Examples/test_spatial_filters_2sources.m new file mode 100644 index 0000000..a88990d --- /dev/null +++ b/Examples/test_spatial_filters_2sources.m @@ -0,0 +1,340 @@ +%% Add latest mrC +clear;clc +mrCFolder = fileparts(fileparts(mfilename('fullpath')));%'/Users/kohler/code/git'; +addpath(genpath(mrCFolder)); +addpath('../../../BrewerMap/') +%% +DestPath = 'ExampleData2'; +AnatomyPath = fullfile(DestPath,'anatomy'); +ProjectPath = fullfile(DestPath,'FwdProject'); + +% Pre-select ROIs +[RoiList,subIDs] = mrC.Simulate.GetRoiClass(ProjectPath,AnatomyPath);% 13 subjects with Wang atlab +Wangs = cellfun(@(x) {x.getAtlasROIs('wang')},RoiList); +Wangnums = cellfun(@(x) x.ROINum,Wangs)>0; + +% define noise properties +Noise.mu.pink=2; +Noise.mu.alpha=2; +Noise.mu.sensor=2; + +% define locations of sources +%--------------------------Cond1: V2d_R ----------------------------- +Rois1 = cellfun(@(x) x.searchROIs('V2d','wang','R'),RoiList,'UniformOutput',false);% % wang ROI +Rois2 = cellfun(@(x) x.searchROIs('LO1','wang','L'),RoiList,'UniformOutput',false); +RoisI = cellfun(@(x,y) x.mergROIs(y),Rois1,Rois2,'UniformOutput',false); +do_new_data_generation = false; +% generate or read from disk +if ~exist('data_for_spatial_filter_test_2source.mat','file') | do_new_data_generation + n_trials = 1000 ; + Noise.lambda = 0 ; % noise only + [outSignal, FundFreq, SF]= mrC.Simulate.ModelSeedSignal('signalType','SSVEP','signalFreq',[2 2],'signalHarmonic',{[2,0,1.5,0],[1.5,0, 2,0]},'signalPhase',{[.1,0,.1,0],[pi/2+.1,0,pi/2+.1,0]}); + [EEGData_noise,EEGAxx_noise,EEGData_signal,EEGAxx_signal,~,masterList,subIDs,allSubjFwdMatrices,allSubjRois] = mrC.Simulate.SimulateProject(ProjectPath,'anatomyPath',AnatomyPath,'signalArray',outSignal,'signalFF',FundFreq,'signalsf',SF,'NoiseParams',Noise,'rois',RoisI,'Save',false,'cndNum',1,'nTrials',n_trials); + save('data_for_spatial_filter_test2_2source.mat'); + save('data_for_spatial_filter_test_2source.mat','EEGAxx_noise','EEGData_noise','-v7.3'); +else + load('data_for_spatial_filter_test2_2source.mat') + load('data_for_spatial_filter_test_2source.mat') +end + +%% +% mix signal and nose according to SNR and convert to Axx +opt.signalFF=FundFreq(1) ; +opt.signalsf=SF ; +opt.cndNum = 1; + +% note that this SNR is defined over the full spectrum, while the signal is +% narrowbanded +% SNR parameters +F1 = EEGAxx_signal{1,1}.i1F1; +Lambda_list = 1:2:10; + +% spatial filter test parameters +fund_freq_idx = 1 ; +numTrials_list = 20;%2.^[1,2,3,4,5,6,7]; +nDraws = 20 ; +n_comps = 3 ; +thisFundFreq = FundFreq(fund_freq_idx) ; + +subs = num2cell(1:10) ; %%%%%% SUBJECTS TO SELECT +subNames = cellfun(@num2str,subs(1:10),'uni',false); + +EEGData_noise = cellfun(@(x) x(:,:,1:200),EEGData_noise,'uni',false); % reduce data size +EEGAxx_noise = cellfun(@(x) x.SelectTrials(1:200),EEGAxx_noise,'uni',false); + +rois = allSubjRois(cell2mat(subs)) ; +fwdMatrix = allSubjFwdMatrices(cell2mat(subs)) ; +Source_pattern = zeros(size(fwdMatrix{1},1),rois{1}.ROINum,numel(subs)) ; +for sub = 1:numel(subs) + for roi_idx = 1:rois{1}.ROINum + % assuming uniform activation + Source_pattern(:,roi_idx,sub) = sum(fwdMatrix{subs{sub}}(:,rois{subs{sub}}.ROIList(roi_idx).meshIndices ),2) ; + end +end + +for nLambda_idx = 1:numel(Lambda_list) + + + % define SNR in a narrow frequency bands on first forth harmonics + lambda = Lambda_list(nLambda_idx); + disp(['Generating EEG by adding signal and noise: SNR = ' num2str(lambda)]); + for subj_idx = 1:length(EEGData_signal) + [Sig] = mean(EEGAxx_signal{subj_idx}.Amp(F1+1,:).^2,2); + Noi = mean(mean(EEGAxx_noise{subj_idx}.Amp(F1:F1+1,:).^2,2)); + EEGData{subj_idx} = sqrt(Noi/Sig)*sqrt(lambda/(1+lambda))*EEGData_signal{subj_idx} + sqrt(1/(1+lambda)) * EEGData_noise{subj_idx} ; + EEGAxx{subj_idx} = mrC.Simulate.CreateAxx(EEGData{subj_idx},opt) ; + end + + % test spatial filters + for s = 1:numel(subs) + display(['Calculating spatial filters for subject:' subNames{s}]); + subj_idx = subs{s}; + + source_pattern = Source_pattern(:,:,subj_idx ); + + decomp_methods = {'pca','ssd','csp','rca'} ; + considered_harms=[1,2] ; + + for nTrial_idx = 1:length(numTrials_list) + nUsedTrials = numTrials_list(nTrial_idx); + for draw_idx = 1:nDraws + fprintf('nUsedTrials = %i, draw_idx = %i \n',nUsedTrials, draw_idx) ; + random_numbers = randperm(EEGAxx_noise{1}.nTrl) ; + % todo: improve indexing to avoid identical trials in different + % draws. requires simulation of enough trials + % for starters: just take random trials + trial_idxs = random_numbers(1:nUsedTrials); + + thisAxx = cellfun(@(x) x.SelectTrials(trial_idxs),EEGAxx(subj_idx),'uni',false); + T = thisAxx{1}; + for sub = 2:numel(thisAxx) + T = T.MergeTrials(thisAxx{sub}); + end + thisAxx = T; + thisTempMean = mean(mean(thisAxx.Wave,3),1) ; + + % make sure the no-stimulation condition does not see the same + % noise component + noise_trial_idxs = random_numbers(nUsedTrials+1:2*nUsedTrials); + thisNoiseAxx = cellfun(@(x) x.SelectTrials(trial_idxs),EEGAxx_noise(subj_idx),'uni',false); + T = thisNoiseAxx{1}; + for sub = 2:numel(thisNoiseAxx) + T = T.MergeTrials(thisNoiseAxx{sub}); + end + thisNoiseAxx = T; + + for decomp_method_idx = 1:length(decomp_methods) + this_decomp_method = decomp_methods{decomp_method_idx}; + + if strcmpi(this_decomp_method,'pca') + [thisDecompAxx,thisW,thisA,thisD] = mrC.SpatialFilters.PCA(thisAxx,'freq_range',thisFundFreq*considered_harms); + elseif strcmpi(this_decomp_method,'pca_cart') + [thisDecompAxx,thisW,thisA,thisD] = mrC.SpatialFilters.PCA(thisAxx,'freq_range',thisFundFreq*considered_harms,'model_type','cartesian'); + elseif strcmpi(this_decomp_method,'fullfreqPca') + [thisDecompAxx,thisW,thisA,thisD] = mrC.SpatialFilters.PCA(thisAxx); + elseif strcmpi(this_decomp_method,'fullfreqPca') + [thisDecompAxx,thisW,thisA,thisD] = mrC.SpatialFilters.PCA(thisAxx,'freq_range',[1:50]); + elseif strcmpi(this_decomp_method,'tpca') + [thisDecompAxx,thisW,thisA,thisD] = mrC.SpatialFilters.tPCA(thisAxx); + elseif strcmpi(this_decomp_method,'ssd') + [thisDecompAxx,thisW,thisA,thisD]= mrC.SpatialFilters.SSD(thisAxx,thisFundFreq*considered_harms,'do_whitening',true); + elseif strcmpi(this_decomp_method,'rca') + [thisDecompAxx,thisW,thisA,thisD] = mrC.SpatialFilters.RCA(thisAxx,'freq_range',thisFundFreq*considered_harms,'do_whitening',true); + + elseif strcmpi(this_decomp_method,'csp') + [theseDecompAxxs,thisW,thisA,thisD] = mrC.SpatialFilters.CSP({thisAxx,thisNoiseAxx},'freq_range',thisFundFreq*considered_harms,'do_whitening',true); + thisDecompAxx=theseDecompAxxs{1}; + end + for i = 1:size(thisA,2) + if source_pattern(:,i)'*thisA(:,i)<0 + thisA(:,i) = thisA(:,i)*-1 ; + end + end + Axx_compspace.(this_decomp_method){s}{nLambda_idx}{draw_idx} = thisDecompAxx ; + W.(this_decomp_method){s}{nLambda_idx}{draw_idx} = thisW ; + A.(this_decomp_method){s}{nLambda_idx}{draw_idx} = thisA ; + D.(this_decomp_method){s}{nLambda_idx}{draw_idx} = thisD ; + + % metrics for first 2 components + % calculate error angles + freqs = [0:thisDecompAxx.nFr]*thisDecompAxx.dFHz; + signal_freq_idxs = find(ismember(freqs,thisFundFreq*considered_harms)); + noise_freq_idxs = [signal_freq_idxs-1,signal_freq_idxs+1] ; + + %err_angles.(this_decomp_method)(comp_idx,nTrial_idx,draw_idx) = 180/pi* acos(abs(source_pattern(:,1)'*thisA(:,comp_idx))/sqrt(sum(source_pattern(:,1).^2)*sum(thisA(:,comp_idx).^2))) ; + err_angles.(this_decomp_method){s}(:,1:size(thisA,2),nLambda_idx,draw_idx) = 180/pi* acos(abs(source_pattern'*thisA)./sqrt(repmat(sum(source_pattern.^2)',[1 size(thisA,2)]).*repmat(sum(thisA.^2),[size(source_pattern,2) 1]))) ; +% if abs(imag(err_angles.(this_decomp_method)(comp_idx,nTrial_idx,draw_idx)))>10^-10 +% error('angle should not be complex') +% else + err_angles.(this_decomp_method){s}(:,:,nLambda_idx,draw_idx)=... + real(err_angles.(this_decomp_method){s}(:,:,nLambda_idx,draw_idx)); +% end + + %calculate snrs assuming ssveps, mean over all trials + snrs.(this_decomp_method){s}(1:size(thisA,2),nLambda_idx,draw_idx)=mean(2*mean(thisDecompAxx.Amp(signal_freq_idxs,:,:).^2)./mean(thisDecompAxx.Amp(noise_freq_idxs,:,:).^2),3); + % calculate residuals as mse over samples and trials + % TODO: needs some sort of normalization!! + est_signal = squeeze(thisDecompAxx.Wave ); + ref_signal = outSignal(1:100,:);%squeeze(repmat(EEGAxx_signal{1}.Wave(:,1,:),1,1,size(thisDecompAxx.Wave,3))) ; + % normalize to equal power before calculating residual + est_signal = est_signal./sqrt(mean(est_signal.^2,1)); + est_signal = repmat(mean(est_signal,3),[1 1 size(ref_signal,2)]); + + ref_signal = ref_signal./sqrt(mean(ref_signal.^2,1)); + ref_signal = permute(repmat(ref_signal,[1 1 size(est_signal,2)]),[1 3 2]); + + residuals.(this_decomp_method){s}(:,1:size(thisA,2),nLambda_idx,draw_idx) =... + squeeze(min(... + mean((ref_signal-est_signal).^2),... + mean((ref_signal+est_signal).^2)))'; + end + end + end + + % + + end + EEGData = {}; + EEGAxx = {} ; +end + +%% +% scalp plots +Subject_idx = 1; +source_pattern = Source_pattern(:,:,Subject_idx); +n_comps = 2; +decomp_methods = {'pca','ssd'}; +FigH = figure('DefaultAxesPosition', [0.1, 0.1, 0.8, 0.8]); +nCols = 1+n_comps*length(decomp_methods) ; +nRows = length(Lambda_list); +set(FigH,'Unit','Inch','position',[5, 5, 18, 9],'color','w'); +rowCounter = 1 ; + +for src = 1:2%n_comps + S = subplot_tight(nRows,nCols,1+nCols*(floor(nRows/2)+src-1),[-0.01,-0.005]); + set(S,'position',get(S,'position')+[.05 0 0 0]) + mrC.Simulate.PlotScalp(source_pattern(:,src),['Source' num2str(src)]); + caxis([-max(abs(source_pattern(:,src))) max(abs(source_pattern(:,src)))]); +end + +for nLambda_idx = 1:length(Lambda_list) + colCounter = 1 ; + for decomp_method_idx=1:length(decomp_methods) + this_decomp_method = decomp_methods{decomp_method_idx}; + for comp_idx = 1:min(n_comps, size(A.(this_decomp_method){Subject_idx}{nLambda_idx}{1},2 )) + Sub = subplot_tight(nRows,nCols,1+(colCounter-1)*n_comps+comp_idx+(rowCounter-1)*nCols,[-0.01,-0.005]); + set(Sub,'position',get(Sub,'position')+[0.05-(comp_idx*.025) -0.02 0 0]); + + Topo = A.(this_decomp_method){Subject_idx}{nLambda_idx}{1}(:,comp_idx); + if abs(min(Topo))>max(Topo), Topo = -1*Topo;end + mrC.Simulate.PlotScalp(Topo); + if comp_idx ==1 + this_title = sprintf('SNR = %i (dB)',round(10*log10(Lambda_list(nLambda_idx)))); + T = title(this_title); + set(T,'position',get(T,'position')+[2 0 0]); + end + + if rowCounter == 1 + % axes('position',[.18+(.10*(comp_idx-1))+(.22*(decomp_method_idx-1)) .98 .2 .1 ]); + % this_title = sprintf('%s %1i',this_decomp_method, comp_idx) ; + % text(0,0,this_title,'fontsize',12,'fontweight','bold'); axis off; + if comp_idx == 1 + axes('position',[.38+(.4*(decomp_method_idx-1))-(.08) .99 .2 .1 ]); + this_title = sprintf('%s %i',this_decomp_method,comp_idx) ; + text(0,0,this_title,'fontsize',12,'fontweight','bold'); axis off; + else + axes('position',[.38+(.4*(decomp_method_idx-1))+.1 .99 .2 .1 ]); + this_title = sprintf('%s %i',this_decomp_method,comp_idx) ; + text(0,0,this_title,'fontsize',12,'fontweight','bold'); axis off; + end + else + this_title = sprintf('%i trials',Lambda_list(nLambda_idx)); + end + caxis([-max(abs(Topo)) max(abs(Topo))]); + end + colCounter = colCounter +1 ; + end + rowCounter = rowCounter+1 ; +end +colormap(jmaColors('coolhotcortex')) +set(FigH,'Unit','Inch','position',[5, 5, 10, 7],'color','w'); +export_fig(FigH,['headplots-twosources_SNR_average'],'-pdf'); +%close; + + %% plots angular error of topographies + FS = 14; +FIG2 = figure; +subplot(1,3,1) +colors = brewermap(4,'Set2') ; +markers = {'-o',':o'}; +for comp_idx = 1:2 +for decomp_method_idx=1:2%length(decomp_methods) + this_decomp_method = decomp_methods{decomp_method_idx}; + err_angles.(this_decomp_method) = cellfun(@(x) x(:,1:10,:,:),err_angles.(this_decomp_method),'uni',false); + errAng_all = squeeze(mean(cat(5,err_angles.(this_decomp_method){:}),4)); + plot(10*log10(Lambda_list),squeeze(mean(errAng_all(comp_idx,comp_idx,:,:),4)),markers{comp_idx},'LineWidth',2,'MarkerSize',10,'color',colors(decomp_method_idx,:)); + + hold on +end +end +%[~, hobj, ~, ~] = legend(decomp_methods(1:2)); +hl = findobj(hobj,'type','line'); +set(hl,'LineWidth',1.5); +ht = findobj(hobj,'type','text'); +set(ht,'FontSize',12); +set(gca,'xtick',10*log10(Lambda_list),'xticklabel',arrayfun(@num2str,round(log10(Lambda_list)*10),'uni',false)); +xlim(10*[0-.1 1.1]); +xlabel('SNR (dB)') +ylabel('Error Angle') + +set(gca,'fontsize',FS) + +% plot snrs +subplot(1,3,2) +comp_idx =1; +for comp_idx = 1:2 + for decomp_method_idx=1:2%length(decomp_methods) + this_decomp_method = decomp_methods{decomp_method_idx}; + snrs.(this_decomp_method) = cellfun(@(x) x(1:10,:,:),snrs.(this_decomp_method),'uni',false); + snrs_all = squeeze(mean(cat(4,snrs.(this_decomp_method){:}),3)); + plot(10*log10(Lambda_list),10*log10(squeeze(mean(snrs_all(comp_idx,:,:),3))),markers{comp_idx},'LineWidth',2,'MarkerSize',10,'color',colors(decomp_method_idx,:)); + hold on + end +end +%[~, hobj, ~, ~] = legend(decomp_methods(1:2)); +hl = findobj(hobj,'type','line'); +set(hl,'LineWidth',1.5); +ht = findobj(hobj,'type','text'); +set(ht,'FontSize',12); +xlabel('SNR (dB)') +ylabel('Output SNR (dB)') +set(gca,'xtick',10*log10(Lambda_list),'xticklabel',arrayfun(@num2str,round(log10(Lambda_list)*10),'uni',false)); +xlim(10*[0-.1 1.1]); +set(gca,'fontsize',FS) + +% plot residual +subplot(1,3,3) +for comp_idx = 1:2 + for decomp_method_idx=1:2%length(decomp_methods) + this_decomp_method = decomp_methods{decomp_method_idx}; + residuals.(this_decomp_method) = cellfun(@(x) x(:,1:10,:,:),residuals.(this_decomp_method),'uni',false); + residuals_all = squeeze(mean(cat(5,residuals.(this_decomp_method){:}),4)); + plot(10*log10(Lambda_list),squeeze(mean(residuals_all(comp_idx,comp_idx,:,:),4)),markers{comp_idx},'LineWidth',2,'MarkerSize',10,'color',colors(decomp_method_idx,:)); + hold on + end +end +%[~, hobj, ~, ~] = legend(decomp_methods(1:2)); +[~, hobj, ~, ~] = legend('pca - comp1','ssd - comp1','pca - comp2','ssd - comp2'); +hl = findobj(hobj,'type','line'); +set(hl,'LineWidth',1.5); +ht = findobj(hobj,'type','text'); +set(ht,'FontSize',11); +xlabel('SNR (dB)') +ylabel('Residuals') +set(gca,'fontsize',FS) +set(gca,'xtick',10*log10(Lambda_list),'xticklabel',arrayfun(@num2str,round(log10(Lambda_list)*10),'uni',false)); +xlim(10*[0-.1 1.1]); + +set(FIG2,'Unit','Inch','position',[5, 5, 18, 5],'color','w'); +export_fig(FIG2,['ErrorPlots_Averaged'],'-pdf'); + diff --git a/tools/BrewerMap/LICENSE.TXT b/tools/BrewerMap/LICENSE.TXT new file mode 100644 index 0000000..d0381d6 --- /dev/null +++ b/tools/BrewerMap/LICENSE.TXT @@ -0,0 +1,176 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/tools/BrewerMap/README.md b/tools/BrewerMap/README.md new file mode 100644 index 0000000..886e3c8 --- /dev/null +++ b/tools/BrewerMap/README.md @@ -0,0 +1,79 @@ +BrewerMap +========= + +The complete palette of ColorBrewer colormaps for MATLAB. Simple selection by scheme name and map length. + + +One function provides the complete selection of the ColorBrewer colorschemes, especially intended for mapping and plots with attractive, distinguishable colors. + +Simple to use: only the the colormap length and the colorscheme name are needed to select and define an output colormap. The colorscheme can be preselected by the user, after which only the colormap length is required to define an output colormap. + +The function can be used as a drop-in replacement for the inbuilt colormap functions and it is compatible with all MATLAB functions that require a colormap. The function consists of just one M-file that provides all of the ColorBrewer colorschemes (no mat file, no third party files, no file-clutter!). Downsampling or interpolation of the nodes occurs automatically, if required (interpolation occurs within the Lab colorspace). As an option, the colormap can be returned reversed. + +Calling brewermap('plot') creates a figure that displays all of the ColorBrewer colorschemes. + +This product includes color specifications and designs developed by Cynthia Brewer (http://colorbrewer.org/). See the ColorBrewer website for further information about each colorscheme, colorblind suitability, licensing, and citations. + +### Examples ### + + % Plot a scheme's RGB values: + rgbplot(brewermap(9,'Blues')) % standard + rgbplot(brewermap(9,'*Blues')) % reversed + + % View information about a colorscheme: + [~,num,typ] = brewermap(0,'Paired') + num = 12 + typ = 'Qualitative' + + % Multiline plot using matrices: + N = 6; + axes('ColorOrder',brewermap(N,'Pastel2'),'NextPlot','replacechildren') + X = linspace(0,pi*3,1000); + Y = bsxfun(@(x,n)n*sin(x+2*n*pi/N), X.', 1:N); + plot(X,Y, 'linewidth',4) + + % Multiline plot in a loop: + N = 6; + set(0,'DefaultAxesColorOrder',brewermap(N,'Accent')) + X = linspace(0,pi*3,1000); + Y = bsxfun(@(x,n)n*sin(x+2*n*pi/N), X.', 1:N); + for n = 1:N + plot(X(:),Y(:,n), 'linewidth',4); + hold all + end + + % New colors for the COLORMAP example: + load spine + image(X) + colormap(brewermap([],'YlGnBu')) + + % New colors for the SURF example: + [X,Y,Z] = peaks(30); + surfc(X,Y,Z) + colormap(brewermap([],'RdYlGn')) + axis([-3,3,-3,3,-10,5]) + + % New colors for the CONTOURCMAP example: + brewermap('PuOr'); % preselect the colorscheme. + load topo + load coast + figure + worldmap(topo, topolegend) + contourfm(topo, topolegend); + contourcmap('brewermap', 'Colorbar','on', 'Location','horizontal',... + 'TitleString','Contour Intervals in Meters'); + plotm(lat, long, 'k') + +### Note ### + +Note that the function BREWERMAP: +* Consists of just one convenient M-file (no .mat files or file clutter). +* No third-party file dependencies. +* Interpolates in the Lab colorspace. +* Requires just the standard ColorBrewer scheme name to select the colorscheme. +* Supports all ColorBrewer colorschemes. +* Outputs a MATLAB standard N-by-3 numeric RGB array. +* Default length is the standard MATLAB default colormap length (same length as the current colormap). +* Is compatible with all MATLAB functions that use colormaps (eg: CONTOURCMAP). +* Includes the option to reverse the colormap color sequence. +* Does not break ColorBrewer's Apache license conditions! diff --git a/tools/BrewerMap/brewermap.m b/tools/BrewerMap/brewermap.m new file mode 100644 index 0000000..78411d6 --- /dev/null +++ b/tools/BrewerMap/brewermap.m @@ -0,0 +1,540 @@ +function [map,num,typ] = brewermap(N,scheme) +% The complete selection of ColorBrewer colorschemes (RGB colormaps). +% +% (c) 2014 Stephen Cobeldick +% +% Returns any RGB colormap from the ColorBrewer colorschemes, especially +% intended for mapping and plots with attractive, distinguishable colors. +% +%%% Syntax (basic): +% map = brewermap(N,scheme); % Select colormap length, select any colorscheme. +% brewermap('plot') % View a figure showing all ColorBrewer colorschemes. +% schemes = brewermap('list')% Return a list of all ColorBrewer colorschemes. +% [map,num,typ] = brewermap(...); % The current colorscheme's number of nodes and type. +% +%%% Syntax (preselect colorscheme): +% old = brewermap(scheme); % Preselect any colorscheme, return the previous scheme. +% map = brewermap(N); % Use preselected scheme, select colormap length. +% map = brewermap; % Use preselected scheme, length same as current figure's colormap. +% +% See also CUBEHELIX RGBPLOT3 RGBPLOT COLORMAP COLORBAR PLOT PLOT3 SURF IMAGE AXES SET JET LBMAP PARULA +% +%% Color Schemes %% +% +% This product includes color specifications and designs developed by Cynthia Brewer. +% See the ColorBrewer website for further information about each colorscheme, +% colour-blind suitability, licensing, and citations: http://colorbrewer.org/ +% +% To reverse the colormap sequence simply prefix the string token with '*'. +% +% Each colorscheme is defined by a set of hand-picked RGB values (nodes). +% If is greater than the requested colorscheme's number of nodes then: +% * Sequential and Diverging schemes are interpolated to give a larger +% colormap. The interpolation is performed in the Lab colorspace. +% * Qualitative schemes are repeated to give a larger colormap. +% Else: +% * Exact values from the ColorBrewer sequences are returned for all colorschemes. +% +%%% Diverging +% +% Scheme|'BrBG'|'PRGn'|'PiYG'|'PuOr'|'RdBu'|'RdGy'|'RdYlBu'|'RdYlGn'|'Spectral'| +% ------|------|------|------|------|------|------|--------|--------|----------| +% Nodes | 11 | 11 | 11 | 11 | 11 | 11 | 11 | 11 | 11 | +% +%%% Qualitative +% +% Scheme|'Accent'|'Dark2'|'Paired'|'Pastel1'|'Pastel2'|'Set1'|'Set2'|'Set3'| +% ------|--------|-------|--------|---------|---------|------|------|------| +% Nodes | 8 | 8 | 12 | 9 | 8 | 9 | 8 | 12 | +% +%%% Sequential +% +% Scheme|'Blues'|'BuGn'|'BuPu'|'GnBu'|'Greens'|'Greys'|'OrRd'|'Oranges'|'PuBu'| +% ------|-------|------|------|------|--------|-------|------|---------|------| +% Nodes | 9 | 9 | 9 | 9 | 9 | 9 | 9 | 9 | 9 | +% +% Scheme|'PuBuGn'|'PuRd'|'Purples'|'RdPu'|'Reds'|'YlGn'|'YlGnBu'|'YlOrBr'|'YlOrRd'| +% ------|--------|------|---------|------|------|------|--------|--------|--------| +% Nodes | 9 | 9 | 9 | 9 | 9 | 9 | 9 | 9 | 9 | +% +%% Examples %% +% +%%% Plot a scheme's RGB values: +% rgbplot(brewermap(9,'Blues')) % standard +% rgbplot(brewermap(9,'*Blues')) % reversed +% +%%% View information about a colorscheme: +% [~,num,typ] = brewermap(0,'Paired') +% num = 12 +% typ = 'Qualitative' +% +%%% Multi-line plot using matrices: +% N = 6; +% axes('ColorOrder',brewermap(N,'Pastel2'),'NextPlot','replacechildren') +% X = linspace(0,pi*3,1000); +% Y = bsxfun(@(x,n)n*sin(x+2*n*pi/N), X(:), 1:N); +% plot(X,Y, 'linewidth',4) +% +%%% Multi-line plot in a loop: +% set(0,'DefaultAxesColorOrder',brewermap(NaN,'Accent')) +% N = 6; +% X = linspace(0,pi*3,1000); +% Y = bsxfun(@(x,n)n*sin(x+2*n*pi/N), X(:), 1:N); +% for n = 1:N +% plot(X(:),Y(:,n), 'linewidth',4); +% hold all +% end +% +%%% New colors for the COLORMAP example: +% load spine +% image(X) +% colormap(brewermap([],'YlGnBu')) +% +%%% New colors for the SURF example: +% [X,Y,Z] = peaks(30); +% surfc(X,Y,Z) +% colormap(brewermap([],'RdYlGn')) +% axis([-3,3,-3,3,-10,5]) +% +%%% New colors for the CONTOURCMAP example: +% brewermap('PuOr'); % preselect the colorscheme. +% load topo +% load coast +% figure +% worldmap(topo, topolegend) +% contourfm(topo, topolegend); +% contourcmap('brewermap', 'Colorbar','on', 'Location','horizontal',... +% 'TitleString','Contour Intervals in Meters'); +% plotm(lat, long, 'k') +% +%% Input and Output Arguments %% +% +%%% Inputs (*=default): +% N = NumericScalar, N>=0, an integer to specify the colormap length. +% = *[], same length as the current figure's colormap (see COLORMAP). +% = NaN, same length as the defining RGB nodes (useful for Line ColorOrder). +% = CharRowVector, to preselect a ColorBrewer colorscheme for later use. +% = 'plot', create a figure showing all of the ColorBrewer colorschemes. +% = 'list', return a cell array of strings listing all ColorBrewer colorschemes. +% scheme = CharRowVector, a ColorBrewer colorscheme name. +% = *none, uses the preselected colorscheme (must be set previously!). +% +%%% Outputs: +% map = NumericMatrix, size Nx3, a colormap of RGB values between 0 and 1. +% num = NumericScalar, the number of nodes defining the ColorBrewer colorscheme. +% typ = CharRowVector, the colorscheme type: 'Diverging'/'Qualitative'/'Sequential'. +% OR +% schemes = CellOfCharRowVectors, a list of every ColorBrewer colorscheme. +% +% [map,num,typ] = brewermap(*N,*scheme) +% OR +% schemes = brewermap('list') + +%% Input Wrangling %% +% +persistent raw tok isr idp +% +if isempty(raw) + raw = bmColors(); +end +% +msg = 'A colorscheme must be preselected before calling without a colorscheme name.'; +% +if nargin==0 % Current figure's colormap length and the preselected colorscheme. + assert(~isempty(idp),msg) + [map,num,typ] = bmSample([],isr,raw(idp)); +elseif nargin==2 % Input colormap length and colorscheme. + assert(isnumeric(N),'The first argument must be a scalar numeric, or empty.') + assert(ischar(scheme)&&isrow(scheme),'The second argument must be a 1xN char.') + tmp = strncmp('*',scheme,1); + [map,num,typ] = bmSample(N,tmp,raw(bmMatch(scheme,tmp,raw))); +elseif isnumeric(N) % Input colormap length and the preselected colorscheme. + assert(~isempty(idp),msg) + [map,num,typ] = bmSample(N,isr,raw(idp)); +else + assert(ischar(N)&&isrow(N),'The first argument must be a 1xN char or a scalar numeric.') + switch lower(N) + case 'plot' % Plot all colorschemes in a figure. + bmPlotFig(raw) + case 'list' % Return a list of all colorschemes. + map = {raw.str}; + typ = {raw.typ}; + num = [raw.num]; + otherwise % Store the preselected colorscheme token. + tmp = strncmp('*',N,1); + idp = bmMatch(N,tmp,raw); + typ = raw(idp).typ; + num = raw(idp).num; + % Only update persistent values if colorscheme name is okay: + isr = tmp; + map = tok; + tok = N; + end +end +% +end +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%brewermap +function idx = bmMatch(str,tmp,raw) +% Match the requested colorscheme name to names in the raw data structure. +str = str(1+tmp:end); +idx = strcmpi({raw.str},str); +assert(any(idx),'Colorscheme "%s" is not supported. Check the colorscheme list.',str) +end +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%bmMatch +function [map,num,typ] = bmSample(N,isr,raw) +% Pick a colorscheme, downsample/interpolate to the requested colormap length. +% +num = raw.num; +typ = raw.typ; +% +if isempty(N) + N = size(get(gcf,'colormap'),1); +elseif isscalar(N)&&isnan(N) + N = num; +else + assert(isscalar(N),'First argument must be a numeric scalar, or empty.') + assert(isreal(N),'Input must be a real numeric: %g+%gi',N,imag(N)) + assert(fix(N)==N&&N>=0,'Input must be positive integer: %g',N) +end +% +if N==0 + map = nan(0,3); + return +end +% +% downsample: +[idx,itp] = bmIndex(N,num,typ); +map = raw.rgb(idx,:)/255; +% interpolate: +if itp + M = [... + +3.2406255,-1.5372080,-0.4986286;... + -0.9689307,+1.8757561,+0.0415175;... + +0.0557101,-0.2040211,+1.0569959]; + wpt = [0.95047,1,1.08883]; % D65 + % + map = bmRGB2Lab(map,M,wpt); % optional + % + % Extrapolate a small amount at both ends: + %vec = linspace(0,num+1,N+2); + %map = interp1(1:num,map,vec(2:end-1),'linear','extrap'); + % Interpolation completely within ends: + map = interp1(1:num,map,linspace(1,num,N),'spline'); + % + map = bmLab2RGB(map,M,wpt); % optional +end +% reverse order: +if isr + map = map(end:-1:1,:); +end +% +end +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%bmSample +function rgb = bmGammaCor(rgb) +% Gamma correction of sRGB data. +idx = rgb <= 0.0031308; +rgb(idx) = 12.92 * rgb(idx); +rgb(~idx) = real(1.055 * rgb(~idx).^(1/2.4) - 0.055); +end +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%bmGammaCor +function rgb = bmGammaInv(rgb) +% Inverse gamma correction of sRGB data. +idx = rgb <= 0.04045; +rgb(idx) = rgb(idx) / 12.92; +rgb(~idx) = real(((rgb(~idx) + 0.055) / 1.055).^2.4); +end +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%bmGammaInv +function lab = bmRGB2Lab(rgb,M,wpt) % Nx3 <- Nx3 +% Convert a matrix of sRGB values to Lab. +% +%applycform(rgb,makecform('srgb2lab','AdaptedWhitePoint',wpt)) +% +% RGB2XYZ: +xyz = bmGammaInv(rgb) / M.'; +% Remember to include my license when copying my implementation. +% XYZ2Lab: +xyz = bsxfun(@rdivide,xyz,wpt); +idx = xyz>(6/29)^3; +F = idx.*(xyz.^(1/3)) + ~idx.*(xyz*(29/6)^2/3+4/29); +lab(:,2:3) = bsxfun(@times,[500,200],F(:,1:2)-F(:,2:3)); +lab(:,1) = 116*F(:,2) - 16; +% +end +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%bmRGB2Lab +function rgb = bmLab2RGB(lab,M,wpt) % Nx3 <- Nx3 +% Convert a matrix of Lab values to sRGB. +% +%applycform(lab,makecform('lab2srgb','AdaptedWhitePoint',wpt)) +% +% Lab2XYZ +tmp = bsxfun(@rdivide,lab(:,[2,1,3]),[500,Inf,-200]); +tmp = bsxfun(@plus,tmp,(lab(:,1)+16)/116); +idx = tmp>(6/29); +tmp = idx.*(tmp.^3) + ~idx.*(3*(6/29)^2*(tmp-4/29)); +xyz = bsxfun(@times,tmp,wpt); +% Remember to include my license when copying my implementation. +% XYZ2RGB +rgb = max(0,min(1, bmGammaCor(xyz * M.'))); +% +end +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%cbLab2RGB +function bmPlotFig(raw) +% Creates a figure showing all of the ColorBrewer colorschemes. +% +persistent cbh axh +% +xmx = max([raw.num]); +ymx = numel(raw); +% +if ishghandle(cbh) + figure(cbh); + delete(axh); +else + cbh = figure('HandleVisibility','callback', 'IntegerHandle','off',... + 'NumberTitle','off', 'Name',[mfilename,' Plot'],'Color','white',... + 'MenuBar','figure', 'Toolbar','none', 'Tag',mfilename); + set(cbh,'Units','pixels') + pos = get(cbh,'Position'); + pos(1:2) = pos(1:2) - 123; + pos(3:4) = max(pos(3:4),[842,532]); + set(cbh,'Position',pos) +end +% +axh = axes('Parent',cbh, 'Color','none',... + 'XTick',0:xmx, 'YTick',0.5:ymx, 'YTickLabel',{raw.str}, 'YDir','reverse'); +title(axh,['ColorBrewer Color Schemes (',mfilename,'.m)'], 'Interpreter','none') +xlabel(axh,'Scheme Nodes') +ylabel(axh,'Scheme Name') +axf = get(axh,'FontName'); +% +for y = 1:ymx + num = raw(y).num; + typ = raw(y).typ; + map = raw(y).rgb(bmIndex(num,num,typ),:)/255; % downsample + for x = 1:num + patch([x-1,x-1,x,x],[y-1,y,y,y-1],1, 'FaceColor',map(x,:), 'Parent',axh) + end + text(xmx+0.1,y-0.5,typ, 'Parent',axh, 'FontName',axf) +end +% +drawnow() +% +end +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%bmPlotFig +function [idx,itp] = bmIndex(N,num,typ) +% Ensure exactly the same colors as the online ColorBrewer colorschemes. +% +itp = N>num; +switch typ + case 'Qualitative' + itp = false; + idx = 1+mod(0:N-1,num); + case 'Diverging' + switch N + case 1 % extrapolated + idx = 8; + case 2 % extrapolated + idx = [4,12]; + case 3 + idx = [5,8,11]; + case 4 + idx = [3,6,10,13]; + case 5 + idx = [3,6,8,10,13]; + case 6 + idx = [2,5,7,9,11,14]; + case 7 + idx = [2,5,7,8,9,11,14]; + case 8 + idx = [2,4,6,7,9,10,12,14]; + case 9 + idx = [2,4,6,7,8,9,10,12,14]; + case 10 + idx = [1,2,4,6,7,9,10,12,14,15]; + otherwise + idx = [1,2,4,6,7,8,9,10,12,14,15]; + end + case 'Sequential' + switch N + case 1 % extrapolated + idx = 6; + case 2 % extrapolated + idx = [4,8]; + case 3 + idx = [3,6,9]; + case 4 + idx = [2,5,7,10]; + case 5 + idx = [2,5,7,9,11]; + case 6 + idx = [2,4,6,7,9,11]; + case 7 + idx = [2,4,6,7,8,10,12]; + case 8 + idx = [1,3,4,6,7,8,10,12]; + otherwise + idx = [1,3,4,6,7,8,10,11,13]; + end + otherwise + error('The colorscheme type "%s" is not recognized',typ) +end +% +end +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%bmIndex +function raw = bmColors() +% Return a structure of all colorschemes: name, scheme type, RGB values, number of nodes. +% Order: first sort by , then case-insensitive sort by : +raw(35).str = 'YlOrRd'; +raw(35).typ = 'Sequential'; +raw(35).rgb = [255,255,204;255,255,178;255,237,160;254,217,118;254,204,92;254,178,76;253,141,60;252,78,42;240,59,32;227,26,28;189,0,38;177,0,38;128,0,38]; +raw(34).str = 'YlOrBr'; +raw(34).typ = 'Sequential'; +raw(34).rgb = [255,255,229;255,255,212;255,247,188;254,227,145;254,217,142;254,196,79;254,153,41;236,112,20;217,95,14;204,76,2;153,52,4;140,45,4;102,37,6]; +raw(33).str = 'YlGnBu'; +raw(33).typ = 'Sequential'; +raw(33).rgb = [255,255,217;255,255,204;237,248,177;199,233,180;161,218,180;127,205,187;65,182,196;29,145,192;44,127,184;34,94,168;37,52,148;12,44,132;8,29,88]; +raw(32).str = 'YlGn'; +raw(32).typ = 'Sequential'; +raw(32).rgb = [255,255,229;255,255,204;247,252,185;217,240,163;194,230,153;173,221,142;120,198,121;65,171,93;49,163,84;35,132,67;0,104,55;0,90,50;0,69,41]; +raw(31).str = 'Reds'; +raw(31).typ = 'Sequential'; +raw(31).rgb = [255,245,240;254,229,217;254,224,210;252,187,161;252,174,145;252,146,114;251,106,74;239,59,44;222,45,38;203,24,29;165,15,21;153,0,13;103,0,13]; +raw(30).str = 'RdPu'; +raw(30).typ = 'Sequential'; +raw(30).rgb = [255,247,243;254,235,226;253,224,221;252,197,192;251,180,185;250,159,181;247,104,161;221,52,151;197,27,138;174,1,126;122,1,119;122,1,119;73,0,106]; +raw(29).str = 'Purples'; +raw(29).typ = 'Sequential'; +raw(29).rgb = [252,251,253;242,240,247;239,237,245;218,218,235;203,201,226;188,189,220;158,154,200;128,125,186;117,107,177;106,81,163;84,39,143;74,20,134;63,0,125]; +raw(28).str = 'PuRd'; +raw(28).typ = 'Sequential'; +raw(28).rgb = [247,244,249;241,238,246;231,225,239;212,185,218;215,181,216;201,148,199;223,101,176;231,41,138;221,28,119;206,18,86;152,0,67;145,0,63;103,0,31]; +raw(27).str = 'PuBuGn'; +raw(27).typ = 'Sequential'; +raw(27).rgb = [255,247,251;246,239,247;236,226,240;208,209,230;189,201,225;166,189,219;103,169,207;54,144,192;28,144,153;2,129,138;1,108,89;1,100,80;1,70,54]; +raw(26).str = 'PuBu'; +raw(26).typ = 'Sequential'; +raw(26).rgb = [255,247,251;241,238,246;236,231,242;208,209,230;189,201,225;166,189,219;116,169,207;54,144,192;43,140,190;5,112,176;4,90,141;3,78,123;2,56,88]; +raw(25).str = 'Oranges'; +raw(25).typ = 'Sequential'; +raw(25).rgb = [255,245,235;254,237,222;254,230,206;253,208,162;253,190,133;253,174,107;253,141,60;241,105,19;230,85,13;217,72,1;166,54,3;140,45,4;127,39,4]; +raw(24).str = 'OrRd'; +raw(24).typ = 'Sequential'; +raw(24).rgb = [255,247,236;254,240,217;254,232,200;253,212,158;253,204,138;253,187,132;252,141,89;239,101,72;227,74,51;215,48,31;179,0,0;153,0,0;127,0,0]; +raw(23).str = 'Greys'; +raw(23).typ = 'Sequential'; +raw(23).rgb = [255,255,255;247,247,247;240,240,240;217,217,217;204,204,204;189,189,189;150,150,150;115,115,115;99,99,99;82,82,82;37,37,37;37,37,37;0,0,0]; +raw(22).str = 'Greens'; +raw(22).typ = 'Sequential'; +raw(22).rgb = [247,252,245;237,248,233;229,245,224;199,233,192;186,228,179;161,217,155;116,196,118;65,171,93;49,163,84;35,139,69;0,109,44;0,90,50;0,68,27]; +raw(21).str = 'GnBu'; +raw(21).typ = 'Sequential'; +raw(21).rgb = [247,252,240;240,249,232;224,243,219;204,235,197;186,228,188;168,221,181;123,204,196;78,179,211;67,162,202;43,140,190;8,104,172;8,88,158;8,64,129]; +raw(20).str = 'BuPu'; +raw(20).typ = 'Sequential'; +raw(20).rgb = [247,252,253;237,248,251;224,236,244;191,211,230;179,205,227;158,188,218;140,150,198;140,107,177;136,86,167;136,65,157;129,15,124;110,1,107;77,0,75]; +raw(19).str = 'BuGn'; +raw(19).typ = 'Sequential'; +raw(19).rgb = [247,252,253;237,248,251;229,245,249;204,236,230;178,226,226;153,216,201;102,194,164;65,174,118;44,162,95;35,139,69;0,109,44;0,88,36;0,68,27]; +raw(18).str = 'Blues'; +raw(18).typ = 'Sequential'; +raw(18).rgb = [247,251,255;239,243,255;222,235,247;198,219,239;189,215,231;158,202,225;107,174,214;66,146,198;49,130,189;33,113,181;8,81,156;8,69,148;8,48,107]; +raw(17).str = 'Set3'; +raw(17).typ = 'Qualitative'; +raw(17).rgb = [141,211,199;255,255,179;190,186,218;251,128,114;128,177,211;253,180,98;179,222,105;252,205,229;217,217,217;188,128,189;204,235,197;255,237,111]; +raw(16).str = 'Set2'; +raw(16).typ = 'Qualitative'; +raw(16).rgb = [102,194,165;252,141,98;141,160,203;231,138,195;166,216,84;255,217,47;229,196,148;179,179,179]; +raw(15).str = 'Set1'; +raw(15).typ = 'Qualitative'; +raw(15).rgb = [228,26,28;55,126,184;77,175,74;152,78,163;255,127,0;255,255,51;166,86,40;247,129,191;153,153,153]; +raw(14).str = 'Pastel2'; +raw(14).typ = 'Qualitative'; +raw(14).rgb = [179,226,205;253,205,172;203,213,232;244,202,228;230,245,201;255,242,174;241,226,204;204,204,204]; +raw(13).str = 'Pastel1'; +raw(13).typ = 'Qualitative'; +raw(13).rgb = [251,180,174;179,205,227;204,235,197;222,203,228;254,217,166;255,255,204;229,216,189;253,218,236;242,242,242]; +raw(12).str = 'Paired'; +raw(12).typ = 'Qualitative'; +raw(12).rgb = [166,206,227;31,120,180;178,223,138;51,160,44;251,154,153;227,26,28;253,191,111;255,127,0;202,178,214;106,61,154;255,255,153;177,89,40]; +raw(11).str = 'Dark2'; +raw(11).typ = 'Qualitative'; +raw(11).rgb = [27,158,119;217,95,2;117,112,179;231,41,138;102,166,30;230,171,2;166,118,29;102,102,102]; +raw(10).str = 'Accent'; +raw(10).typ = 'Qualitative'; +raw(10).rgb = [127,201,127;190,174,212;253,192,134;255,255,153;56,108,176;240,2,127;191,91,23;102,102,102]; +raw(09).str = 'Spectral'; +raw(09).typ = 'Diverging'; +raw(09).rgb = [158,1,66;213,62,79;215,25,28;244,109,67;252,141,89;253,174,97;254,224,139;255,255,191;230,245,152;171,221,164;153,213,148;102,194,165;43,131,186;50,136,189;94,79,162]; +raw(08).str = 'RdYlGn'; +raw(08).typ = 'Diverging'; +raw(08).rgb = [165,0,38;215,48,39;215,25,28;244,109,67;252,141,89;253,174,97;254,224,139;255,255,191;217,239,139;166,217,106;145,207,96;102,189,99;26,150,65;26,152,80;0,104,55]; +raw(07).str = 'RdYlBu'; +raw(07).typ = 'Diverging'; +raw(07).rgb = [165,0,38;215,48,39;215,25,28;244,109,67;252,141,89;253,174,97;254,224,144;255,255,191;224,243,248;171,217,233;145,191,219;116,173,209;44,123,182;69,117,180;49,54,149]; +raw(06).str = 'RdGy'; +raw(06).typ = 'Diverging'; +raw(06).rgb = [103,0,31;178,24,43;202,0,32;214,96,77;239,138,98;244,165,130;253,219,199;255,255,255;224,224,224;186,186,186;153,153,153;135,135,135;64,64,64;77,77,77;26,26,26]; +raw(05).str = 'RdBu'; +raw(05).typ = 'Diverging'; +raw(05).rgb = [103,0,31;178,24,43;202,0,32;214,96,77;239,138,98;244,165,130;253,219,199;247,247,247;209,229,240;146,197,222;103,169,207;67,147,195;5,113,176;33,102,172;5,48,97]; +raw(04).str = 'PuOr'; +raw(04).typ = 'Diverging'; +raw(04).rgb = [127,59,8;179,88,6;230,97,1;224,130,20;241,163,64;253,184,99;254,224,182;247,247,247;216,218,235;178,171,210;153,142,195;128,115,172;94,60,153;84,39,136;45,0,75]; +raw(03).str = 'PRGn'; +raw(03).typ = 'Diverging'; +raw(03).rgb = [64,0,75;118,42,131;123,50,148;153,112,171;175,141,195;194,165,207;231,212,232;247,247,247;217,240,211;166,219,160;127,191,123;90,174,97;0,136,55;27,120,55;0,68,27]; +raw(02).str = 'PiYG'; +raw(02).typ = 'Diverging'; +raw(02).rgb = [142,1,82;197,27,125;208,28,139;222,119,174;233,163,201;241,182,218;253,224,239;247,247,247;230,245,208;184,225,134;161,215,106;127,188,65;77,172,38;77,146,33;39,100,25]; +raw(01).str = 'BrBG'; +raw(01).typ = 'Diverging'; +raw(01).rgb = [84,48,5;140,81,10;166,97,26;191,129,45;216,179,101;223,194,125;246,232,195;245,245,245;199,234,229;128,205,193;90,180,172;53,151,143;1,133,113;1,102,94;0,60,48]; +% number of nodes: +for k = 1:numel(raw) + switch raw(k).typ + case 'Diverging' + raw(k).num = 11; + case 'Qualitative' + raw(k).num = size(raw(k).rgb,1); + case 'Sequential' + raw(k).num = 9; + end +end +% +end +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%bmColors +% Code and Implementation: +% Copyright (c) 2014 Stephen Cobeldick +% Color Values Only: +% Copyright (c) 2002 Cynthia Brewer, Mark Harrower, and The Pennsylvania State University. +% +% Licensed under the Apache License, Version 2.0 (the "License"); +% you may not use this file except in compliance with the License. +% You may obtain a copy of the License at +% +% http://www.apache.org/licenses/LICENSE-2.0 +% +% Unless required by applicable law or agreed to in writing, software +% distributed under the License is distributed on an "AS IS" BASIS, +% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +% See the License for the specific language governing permissions and limitations under the License. +% +% Redistribution and use in source and binary forms, with or without +% modification, are permitted provided that the following conditions are met: +% +% 1. Redistributions as source code must retain the above copyright notice, this +% list of conditions and the following disclaimer. +% +% 2. The end-user documentation included with the redistribution, if any, must +% include the following acknowledgment: "This product includes color +% specifications and designs developed by Cynthia Brewer +% (http://colorbrewer.org/)." Alternately, this acknowledgment may appear in the +% software itself, if and wherever such third-party acknowledgments normally appear. +% +% 4. The name "ColorBrewer" must not be used to endorse or promote products +% derived from this software without prior written permission. For written +% permission, please contact Cynthia Brewer at cbrewer@psu.edu. +% +% 5. Products derived from this software may not be called "ColorBrewer", nor +% may "ColorBrewer" appear in their name, without prior written permission of Cynthia Brewer. +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%license \ No newline at end of file diff --git a/tools/BrewerMap/brewermap_view.m b/tools/BrewerMap/brewermap_view.m new file mode 100644 index 0000000..6066351 --- /dev/null +++ b/tools/BrewerMap/brewermap_view.m @@ -0,0 +1,364 @@ +function [map,scheme] = brewermap_view(N,scheme) +% An interactive figure for ColorBrewer colormap selection. With demo! +% +% (c) 2014 Stephen Cobeldick +% +% View Cynthia Brewer's ColorBrewer colorschemes in a figure. +% +% * Two colorbars give the colorscheme in color and grayscale. +% * A button toggles between 3D-cube and 2D-lineplot of the RGB values. +% * A button toggles an endless cycle through the colorschemes. +% * A button reverses the colormap. +% * 35 buttons select any ColorBrewer colorscheme. +% * Text with the colorscheme's type (Diverging/Qualitative/Sequential) +% * Text with the colorscheme's number of nodes (defining colors). +% +%%% Syntax: +% brewermap_view +% brewermap_view(N) +% brewermap_view(N,scheme) +% brewermap_view([],...) +% brewermap_view({axes/figure handles},...) % see "Adjust External Colormaps" +% [map,scheme] = brewermap_view(...) +% +% Calling the function with an output argument blocks MATLAB execution until +% the figure is deleted: the final colormap and colorscheme are then returned. +% +% See also BREWERMAP CUBEHELIX RGBPLOT COLORMAP COLORMAPEDITOR COLORBAR UICONTROL ADDLISTENER +% +%% Adjust Colormaps of Other Figures or Axes %% +% +%%% Example: +% +% S = load('spine'); +% image(S.X) +% brewermap_view({gca}) +% +% Very useful! Simply provide a cell array of axes or figure handles when +% calling this function, and their colormaps will be updated in real-time: +% note that MATLAB versions <=2010 only support axes handles for this! +% +%% Input and Output Arguments %% +% +%%% Inputs (*=default): +% N = NumericScalar, an integer to define the colormap length. +% = *[], colormap length of one hundred and twenty-eight (128). +% = NaN, same length as the defining RGB nodes (useful for Line ColorOrder). +% = CellArray of axes/figure handles, to be updated by BREWERMAP_VIEW. +% scheme = CharRowVector, a ColorBrewer colorscheme name. +% +%%% Outputs (these block code execution until the figure is closed!): +% map = NumericMatrix, the colormap defined when the figure is closed. +% scheme = CharRowVector, the name of the colorscheme given in . +% +% [map,scheme] = brewermap_view(N,scheme) + +%% Input Wrangling %% +% +persistent ax2D ln2D ax3D pt3D txtH is2D cbAx cbIm pTxt pSld bEig bGrp bRev scm isr +% +new = isempty(ax2D)||~ishghandle(ax2D); +dfn = 128; +upb = false; +hgc = {}; +nmr = dfn; +% +% Parse colormap size: +if nargin==0 || isnumeric(N)&&isempty(N) + N = dfn; +elseif isnumeric(N) + assert(isscalar(N),'Input must be a scalar numeric. NUMEL: %d',numel(N)) + assert(isreal(N),'Input must be a real numeric: %g+%gi',N,imag(N)) + assert(isnan(N)||fix(N)==N&&N>0,'Input must be positive integer: %g',N) + N = double(N); +elseif iscell(N)&&numel(N) + hgc = N(:); + ish = all(1==cellfun('prodofsize',hgc)&cellfun(@ishghandle,hgc)); + assert(ish,'Input may be a cell array of axes handles or figure handles.') + nmr = [cellfun(@(h)size(colormap(h),1),hgc),dfn]; + N = nmr(1); +else + error('Input may be a numeric scalar/empty, or a cell array of handles.') +end +% +[mcs,mun,pyt] = brewermap('list'); +% +% Check BREWERMAP outputs: +ers = 'The function BREWERMAP returned an unexpected %s.'; +assert(all(35==[numel(mcs),numel(mun),numel(pyt)]),ers,'array size') +tmp = find(any(diff(+char(pyt)),2)); +assert(numel(tmp)==2&&all(tmp==[9;17]),ers,'scheme name sequence') +% +% Default pseudo-random colorscheme: +if nargin==0 || new + isr = false; + scm = mcs{1+mod(round(now*1e7),numel(mcs))}; +end +% Parse input colorscheme: +if nargin==2 + assert(ischar(scheme)&&isrow(scheme),'Second input must be a 1xN char vector.') + % Check if a reversed colormap was requested: + isr = strncmp(scheme,'*',1); + scm = scheme(1+isr:end); +end +% +if isnan(N) + N = mun(strcmpi(scm,mcs)); +end +% +%% Ensure Figure Exists %% +% +% LHS and RHS slider bounds/limits, and slider step sizes: +lbd = 1; +rbd = dfn; +stp = [1,10]; % [minor,major] +% +% Define the 3D cube axis order: +xyz = 'RGB'; % choose order +[~,xyz] = ismember(xyz,'RGB'); +% +if new % Create a new figure. + % + % Figure parameters: + M = 9; % buttons per column + gap = 0.01; % gaps + bth = 0.04; % demo height + btw = 0.10; % demo width + uih = 0.40; % height of UI control group + cbw = 0.23; % width of both colorbars + axh = 1-uih-2*gap; % axes height + wdt = 1-cbw-2*gap; % axes width + % + figH = figure('HandleVisibility','callback', 'Color','white',... + 'IntegerHandle','off', 'NumberTitle','off', 'Units','normalized',... + 'Name','ColorBrewer Interactive ColorScheme Selector',... + 'MenuBar','figure', 'Toolbar','none', 'Tag',mfilename); + % + % Add 2D lineplot: + ax2D = axes('Parent',figH, 'Position',[gap,uih+gap,wdt,axh], 'Box','on',... + 'ColorOrder',[1,0,0; 0,1,0; 0,0,1; 0.6,0.6,0.6], 'HitTest','off',... + 'Visible','off', 'XLim',[0,1], 'YLim',[0,1], 'XTick',[], 'YTick',[]); + ln2D = line([0,0,0,0;1,1,1,1],[0,0,0,0;1,1,1,1], 'Parent',ax2D,... + 'Visible','off', 'Linestyle','-', 'Marker','.'); + % + % Add 3D scatterplot: + ax3D = axes('Parent',figH, 'OuterPosition',[0,uih,wdt+2*gap,1-uih],... + 'Visible','on', 'XLim',[0,1], 'YLim',[0,1], 'ZLim',[0,1], 'HitTest','on'); + pt3D = patch('Parent',ax3D, 'XData',[0;1], 'YData',[0;1], 'ZData',[0;1],... + 'Visible','on', 'LineStyle','none', 'FaceColor','none', 'MarkerEdgeColor','none',... + 'Marker','o', 'MarkerFaceColor','flat', 'MarkerSize',10, 'FaceVertexCData',[1,1,0;1,0,1]); + view(ax3D,3); + grid(ax3D,'on') + lbl = {'Red','Green','Blue'}; + xlabel(ax3D,lbl{xyz(1)}) + ylabel(ax3D,lbl{xyz(2)}) + zlabel(ax3D,lbl{xyz(3)}) + % + % Add warning text: + txtH = text('Parent',ax2D, 'Units','normalized', 'Position',[1,1],... + 'HorizontalAlignment','right', 'VerticalAlignment','top', 'Color','k'); + % + % Add demo button: + demo = uicontrol(figH, 'Style','togglebutton', 'Units','normalized',... + 'Position',[gap,uih+gap+0*bth,btw,bth], 'String','Demo',... + 'Max',1, 'Min',0, 'Callback',@bmvDemo); %#ok + % Add 2D/3D button: + is2D = uicontrol(figH, 'Style','togglebutton', 'Units','normalized',... + 'Position',[gap,uih+gap+1*bth,btw,bth], 'String','2D / 3D',... + 'Max',1, 'Min',0, 'Callback',@bmv2D3D); + % Add reverse button: + bRev = uicontrol(figH, 'Style','togglebutton', 'Units','normalized',... + 'Position',[gap,uih+gap+2*bth,btw,bth], 'String','Reverse',... + 'Max',1, 'Min',0, 'Callback',@bmvRevM); + % + % Add colorbars: + C(1,1,:) = [1,1,1]; + cbAx(2) = axes('Parent',figH, 'Visible','off', 'Units','normalized',... + 'Position',[1-cbw/2,gap,cbw/2-gap,1-2*gap], 'YLim',[0.5,1.5],... + 'YDir','reverse', 'HitTest','off'); + cbAx(1) = axes('Parent',figH, 'Visible','off', 'Units','normalized',... + 'Position',[1-cbw/1,gap,cbw/2-gap,1-2*gap], 'YLim',[0.5,1.5],... + 'YDir','reverse', 'HitTest','off'); + cbIm(2) = image('Parent',cbAx(2), 'CData',C); + cbIm(1) = image('Parent',cbAx(1), 'CData',C); + % + % Add parameter slider, listener, and corresponding text: + sv = mean([lbd,rbd],2); + pTxt = uicontrol(figH,'Style','text', 'Units','normalized',... + 'Position',[gap,uih-bth,btw,bth], 'String','X'); + pSld = uicontrol(figH,'Style','slider', 'Units','normalized',... + 'Position',[gap,gap,btw,uih-bth], 'Min',lbd(1), 'Max',rbd(1),... + 'SliderStep',stp(1,:)/(rbd(1)-lbd(1)), 'Value',sv(1)); + addlistener(pSld, 'Value', 'PostSet',@bmvSldr); + % + % Add colorscheme button group: + bGrp = uibuttongroup('Parent',figH, 'BorderType','none', 'Units','normalized',... + 'BackgroundColor','white', 'Position',[2*gap+btw,gap,wdt-btw-gap,uih-gap]); + % Determine button locations: + Z = 1:numel(mcs); + Z = Z+(Z>17); + C = (ceil(Z/M)-1)/4; + R = (M-1-mod(Z-1,M))/M; + % Add colorscheme buttons to group: + for jj = numel(mcs):-1:1 + bEig(jj) = uicontrol('Parent',bGrp, 'Style','Toggle', 'String',mcs{jj},... + 'Unit','normalized', 'Position',[C(jj),R(jj),1/4,1/M]); + end + set(bGrp,'SelectionChangeFcn',@bmvChgS); + % +end +% +set(bGrp,'SelectedObject',bEig(strcmpi(scm,mcs))); +set(pSld,'Value',max(lbd,min(rbd,N))); +% +%% Nested Functions %% +% + function str = makeName() + str = '*'; + str = [str(isr),scm]; + end +% + function bmvUpDt() + % Update all graphics objects in the figure. + % + % Get ColorBrewer colormap and grayscale equivalent: + [map,num,typ] = brewermap(N,makeName()); + mag = map*[0.298936;0.587043;0.114021]; + % + % Update colorbar values: + set(cbAx, 'YLim', [0,abs(N)+(N==0)]+0.5); + set(cbIm(1), 'CData',reshape(map,[],1,3)) + set(cbIm(2), 'CData',repmat(mag,[1,1,3])) + % + % Update 2D line / 3D patch values: + if get(is2D, 'Value') % 2D + set(ln2D, 'XData',linspace(0,1,abs(N))); + set(ln2D,{'YData'},num2cell([map,mag],1).'); + else % 3D + set(pt3D,... + 'XData',map(:,xyz(1)),... + 'YData',map(:,xyz(2)),... + 'ZData',map(:,xyz(3)),... + 'FaceVertexCData',map) + end + % + % Update reverse button: + set(bRev, 'Value',isr) + % + % Update warning text: + str = {[typ,' '];sprintf('%d Nodes ',num)}; + set(txtH,'String',str); + % + % Update parameter value text: + set(pTxt(1), 'String',sprintf('N = %.0f',N)); + % + % Update external axes/figure: + nmr(1) = N; + for ii = find(cellfun(@ishghandle,hgc)) + colormap(hgc{ii},brewermap(nmr(ii),makeName())); + end + % + drawnow() + end +% + function bmv2D3D(h,~) + % Switch between 2D-line and 3D-cube representation. + % + if get(h,'Value') % 2D + set(ax3D, 'HitTest','off', 'Visible','off') + set(ax2D, 'HitTest','on', 'Visible','on') + set(pt3D, 'Visible','off') + set(ln2D, 'Visible','on') + else % 3D + set(ax2D, 'HitTest','off', 'Visible','off') + set(ax3D, 'HitTest','on', 'Visible','on') + set(ln2D, 'Visible','off') + set(pt3D, 'Visible','on') + end + % + bmvUpDt(); + end +% + function bmvChgS(~,e) + % Change the colorscheme. + % + scm = get(e.NewValue,'String'); + % + bmvUpDt() + end +% + function bmvRevM(h,~) + % Reverse the colormap. + % + isr = logical(get(h,'Value')); + % + bmvUpDt() + end +% + function bmvSldr(~,~) + % Update the slider position. + % + N = round(get(pSld,'Value')); + % + bmvUpDt() + end +% + function bmvDemo(h,~) + % Display all ColorBrewer colorschemes sequentially. + % + cnt = uint64(0); + while ishghandle(h)&&get(h,'Value') + cnt = cnt+1; + % + if mod(cnt,23)<1 + ids = 1+mod(find(strcmpi(scm,mcs)),numel(mcs)); + scm = mcs{ids}; + try %#ok + set(bGrp, 'SelectedObject',bEig(ids)); + end + end + % + if mod(cnt,69)<1 + isr = ~isr; + end + % + upb = (upb || N<=1) && N + set(pSld, 'Value',N) + end + % + % Faster/slower: + pause(0.1); + end + % + end +% +%% Initialize the Figure %% +% +bmvUpDt() +% +if nargout + waitfor(ax2D); + scheme = makeName(); +else + clear map +end +% +end +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%brewermap_view +% +% Copyright (c) 2014 Stephen Cobeldick +% +% Licensed under the Apache License, Version 2.0 (the "License"); +% you may not use this file except in compliance with the License. +% You may obtain a copy of the License at +% +% http://www.apache.org/licenses/LICENSE-2.0 +% +% Unless required by applicable law or agreed to in writing, software +% distributed under the License is distributed on an "AS IS" BASIS, +% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +% See the License for the specific language governing permissions and limitations under the License. +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%license \ No newline at end of file diff --git a/tools/plotting/export_fig/.gitignore b/tools/plotting/export_fig/.gitignore new file mode 100644 index 0000000..68c9c92 --- /dev/null +++ b/tools/plotting/export_fig/.gitignore @@ -0,0 +1,5 @@ +/.ignore +*.txt +*.asv +*~ +*.mex* diff --git a/tools/plotting/export_fig/.ignore/pdftops.txt b/tools/plotting/export_fig/.ignore/pdftops.txt deleted file mode 100644 index 720a3ed..0000000 --- a/tools/plotting/export_fig/.ignore/pdftops.txt +++ /dev/null @@ -1 +0,0 @@ -/usr/local/bin/pdftops \ No newline at end of file diff --git a/tools/plotting/export_fig/ImageSelection.class b/tools/plotting/export_fig/ImageSelection.class new file mode 100644 index 0000000000000000000000000000000000000000..cfac41d4d552a6c83b184c77190049bc9ccc5c9a GIT binary patch literal 1092 zcma)5%Wl&^6g^`nabnywl(w{?v`zXT4`_e|3#eG|sDxC(A_b|ia*|2p%5{R{6uyOT zU;*V(2?XrjthEC>baV7;V=+==yzul^5H4_JLiqUj?<69oT_yd;PZbYY%wY3cKzIB%OV` zBLx=Y<}g#cH)yk2wjQZE8&jK(=LB~J3Z;LymY)eE?sr=xo!oXj`FOD3kp7O{a8;%w zgPmg`N{7I$5xUc4mZOQT?R9ET8hf%CP>}iXbyM~Nr|WUq*}r(B{a9ElmCxkEjMI;O zsSkR+t{=#j!pGa5D(|^Kdb8;s8>E+%1!lcF@SAeWQEOiaU93x&(kXaDJ&c7M7A#~j zX~DvTg$nWl*T=uvQ?GxbDOzo~yrQWJERV;P}Zs_nC9HJrT!tW|l&l&#*p}-H`1d-5?S03>np((?7CYjISJmVB^73MXbX5|Q? zQvC$&J#X}#F$3JKmam}YhwGwfEl+pH;EzIq5PO`xw0EqI z^2|}kJn@$>%SwVZrQ{;!7!~6JPoXL#jIpUOx5PNlO`^`?@oaNA`|WU6)W3=}=O{+S fyv}LrmrZ`, where is a positive real number, magnifies the figure by the factor for export, e.g. `-m2` produces an image double the size (in pixels) of the on screen figure; `-r`, again where is a positive real number, specifies the output bitmap to have pixels per inch, the dimensions of the figure (in inches) being those of the on screen figure. For example, using: +```Matlab +export_fig test.png -m2.5 +``` +on the figure from the example above generates: + +![](https://farm4.staticflickr.com/3937/15591910915_dc7040c477_o_d.png) + +Sometimes you might have a figure with an image in. For example: +```Matlab +imshow(imread('cameraman.tif')) +hold on +plot(0:255, sin(linspace(0, 10, 256))*127+128); +set(gcf, 'Position', [100 100 150 150]); +``` +generates this figure: + +![](https://farm4.staticflickr.com/3942/15589249581_ff87a56a3f_o_d.png) + +Here the image is displayed in the figure at resolution lower than its native resolution. However, you might want to export the figure at a resolution such that the image is output at its native (i.e. original) size (in pixels). Ordinarily this would require some non-trivial computation to work out what that resolution should be, but export_fig has an option to do this for you. Using: +```Matlab +export_fig test.png -native +``` +produces: + +![](https://farm6.staticflickr.com/5604/15589249591_da2b2652e4_o_d.png) + +with the image being the size (in pixels) of the original image. Note that if you want an image to be a particular size, in pixels, in the output (other than its original size) then you can resize it to this size and use the `-native` option to achieve this. + +All resolution options (`-m`, `-q` and `-native`) correctly set the resolution information in PNG and TIFF files, as if the image were the dimensions of the on screen figure. + +**Shrinking dots & dashes** - when exporting figures with dashed or dotted lines using either the ZBuffer or OpenGL (default for bitmaps) renderers, the dots and dashes can appear much shorter, even non-existent, in the output file, especially if the lines are thick and/or the resolution is high. For example: +```Matlab +plot(sin(linspace(0, 10, 1000)), 'b:', 'LineWidth', 4); +hold on +plot(cos(linspace(0, 7, 1000)), 'r--', 'LineWidth', 3); +grid on +export_fig test.png +``` +generates: + +![](https://farm4.staticflickr.com/3956/15592747732_f943d4aa0a_o_d.png) + +This problem can be overcome by using the painters renderer. For example: +```Matlab +export_fig test.png -painters +``` +used on the same figure generates: + +![](https://farm4.staticflickr.com/3945/14971168504_77692f11f5_o_d.png) + +Note that not only are the plot lines correct, but the grid lines are too. + +**Transparency** - sometimes you might want a figure and axes' backgrounds to be transparent, so that you can see through them to a document (for example a presentation slide, with coloured or textured background) that the exported figure is placed in. To achieve this, first (optionally) set the axes' colour to 'none' prior to exporting, using: +```Matlab +set(gca, 'Color', 'none'); % Sets axes background +``` + +then use export_fig's `-transparent` option when exporting: +```Matlab +export_fig test.png -transparent +``` + +This will make the background transparent in PDF, EPS and PNG outputs. You can additionally save fully alpha-blended semi-transparent patch objects to the PNG format. For example: + +```Matlab +logo; +alpha(0.5); +``` + +generates a figure like this: + +![](https://farm4.staticflickr.com/3933/15405290339_b08de33528_o_d.png) + +If you then export this to PNG using the `-transparent` option you can then put the resulting image into, for example, a presentation slide with fancy, textured background, like so: + +![](https://farm6.staticflickr.com/5599/15406302920_59beaefff1_o_d.png) + +and the image blends seamlessly with the background. + +**Image quality** - when publishing images of your results, you want them to look as good as possible. By default, when outputting to lossy file formats (PDF, EPS and JPEG), export_fig uses a high quality setting, i.e. low compression, for images, so little information is lost. This is in contrast to MATLAB's print and saveas functions, whose default quality settings are poor. For example: +```Matlab +A = im2double(imread('peppers.png')); +B = randn(ceil(size(A, 1)/6), ceil(size(A, 2)/6), 3) * 0.1; +B = cat(3, kron(B(:,:,1), ones(6)), kron(B(:,:,2), ones(6)), kron(B(:,:,3), ones(6))); +B = A + B(1:size(A, 1),1:size(A, 2),:); +imshow(B); +print -dpdf test.pdf +``` +generates a PDF file, a sub-window of which looks (when zoomed in) like this: + +![](https://farm6.staticflickr.com/5613/15405290309_881b2774d6_o_d.png) + +while the command + +```Matlab +export_fig test.pdf +``` +on the same figure produces this: + +![](https://farm4.staticflickr.com/3947/14971168174_687473133f_o_d.png) + +While much better, the image still contains some compression artifacts (see the low level noise around the edge of the pepper). You may prefer to export with no artifacts at all, i.e. lossless compression. Alternatively, you might need a smaller file, and be willing to accept more compression. Either way, export_fig has an option that can suit your needs: `-q`, where is a number from 0-100, will set the level of lossy image compression (again in PDF, EPS and JPEG outputs only; other formats are lossless), from high compression (0) to low compression/high quality (100). If you want lossless compression in any of those formats then specify a greater than 100. For example: +```Matlab +export_fig test.pdf -q101 +``` +again on the same figure, produces this: + +![](https://farm6.staticflickr.com/5608/15405803908_934512c1fe_o_d.png) + +Notice that all the noise has gone. + +### Tips +**Anti-aliasing** - the anti-aliasing which export_fig applies to bitmap outputs by default makes the images look nice, but it can also blur images and increase exporting time and memory requirements, so you might not always want it. You can set the level of anti-aliasing by using the `-a` option, where is 1 (no anti-aliasing), 2, 3 (default) or 4 (maximum anti-aliasing). + +**Cropping** - by default, export_fig crops its output to minimize the amount of empty space around the figure. If you'd prefer the figure to be uncropped, and instead have the same appearance (in terms of border width) as the on screen figure, then use the `-nocrop` option. + +**Colourspace** - by default, export_fig generates files in the RGB [colourspace](https://en.wikipedia.org/wiki/Color_space). However, you can also export in greyscale or the CMYK colourspace, using the `-grey` (or `-gray`) and `-cmyk` options respectively. The CMYK option is useful for publishers who require documents in this colourspace, but the option is only supported for PDF, EPS and TIFF files. + +**Specifying a target directory** - you can get export_fig to save output files to any directory (for which you have write permission), simply by specifying the full or relative path in the filename. For example: +```Matlab +export_fig ../subdir/fig.png; +export_fig('C:/Users/Me/Documents/figures/myfig', '-pdf', '-png'); +``` + +**Variable file names** - often you might want to save a series of figures in a for loop, each with a different name. For this you can use the functional form of input arguments, i.e. `export_fig(arg1, arg2)`, and construct the filename string in a variable. Here's an example of this: +```Matlab +for a = 1:5 + plot(rand(5, 2)); + export_fig(sprintf('plot%d.png', a)); +end +``` +When using the functional form like this, be sure to put string variables in quotes: +```Matlab +export_fig(sprintf('plot%d', a), '-a1', '-pdf', '-png'); +``` + +**Specifying the figure/axes** - if you have multiple figures open you can specify which figure to export using its handle: +```Matlab +export_fig(figure_handle, filename); +``` +Equally, if your figure contains several subplots then you can export just one of them by giving export_fig the handle to the relevant axes: +```Matlab +export_fig(axes_handle, filename); +``` + +**Multiple formats** - save time by exporting to multiple formats simultaneously. E.g.: +```Matlab +export_fig filename -pdf -eps -png -jpg -tiff +``` + +**Other file formats** - if you'd like to save your figure to a bitmap format that is not supported by export_fig, e.g. animated GIF, PPM file or a frame in a movie, then you can use export_fig to output the image, and optionally an alpha-matte, to the workspace. E.g.: +```Matlab +frame = export_fig; +``` +or +```Matlab +[frame, alpha] = export_fig; +``` +These variables can then be saved to other image formats using other functions, such as imwrite. + +**Appending to a file** - you can use the `-append` option to append the figure to the end of an image/document, if it already exists. This is supported for PDF and TIFF files only. Note that if you wish to append a lot of figures consecutively to a PDF, it can be more efficient to save all the figures to PDF separately then append them all in one go at the end (e.g. using [append_pdfs](http://www.mathworks.com/matlabcentral/fileexchange/31215-appendpdfs)). + +**Output to clipboard** - you can use the `-clipboard` option to copy the specified figure or axes to the system clipboard, for easy paste into other documents (e.g., Word or PowerPoint). Note that the image is copied in bitmap (not vector) format. + +**Font size** - if you want to place an exported figure in a document with the font a particular size then you need to set the font to that size in the figure, and not resize the output of export_fig in the document. To avoid resizing, simply make sure that the on screen figure is the size you want the output to be in the document before exporting. + +**Renderers** - MATLAB has three renderers for displaying and exporting figures: painters, OpenGL and ZBuffer. The different renderers have different [features](http://www.mathworks.com/access/helpdesk/help/techdoc/creating_plots/f3-84337.html#f3-102410), so if you aren't happy with the result from one renderer try another. By default, vector formats (i.e. PDF and EPS outputs) use the painters renderer, while other formats use the OpenGL renderer. Non-default renderers can be selected by using one of these three export_fig input options: `-painters`, `-opengl`, `-zbuffer`: +```Matlab +export_fig test.png -painters +``` + +**Artifacts** - sometimes the output that you get from export_fig is not what you expected. If an output file contains artifacts that aren't in the on screen figure then make sure that the renderer used for rendering the figure on screen is the same as that used for exporting. To set the renderer used to display the figure, use: +```Matlab +set(figure_handle, 'Renderer', 'opengl'); +``` +After matching the two renderers, if the artifact appears in the on screen figure then you'll need to fix that before exporting. Alternatively you can try changing the renderer used by export_fig. Finally check that it isn't one of the known issues mentioned in the section below. + +**Smoothed/interpolated images in output PDF** - if you produce a PDF using export_fig and images in the PDF look overly smoothed or interpolated, this is because the software you are using to view the PDF is smoothing or interpolating the image data. The image is not smoothed in the PDF file itself. If the software has an option to disable this feature, you should select it. Alternatively, use another PDF viewer that doesn't exhibit this problem. + +**Locating Ghostscript/pdftops** - You may find a dialogue box appears when using export_fig, asking you to locate either [Ghostscript](http://www.ghostscript.com) or [pdftops (part of the Xpdf package)](http://www.xpdfreader.com). These are separate applications which export_fig requires to perform certain functions. If such a dialogue appears it is because export_fig can't find the application automatically. This is because you either haven't installed it, or it isn't in the normal place. Make sure you install the applications correctly first. They can be downloaded from the following places: + 1. Ghostscript: [www.ghostscript.com](http://www.ghostscript.com) + 2. pdftops (install the Xpdf package): [www.xpdfreader.com](http://www.xpdfreader.com) + +If you choose to install them in a non-default location then point export_fig +to this location using the dialogue box. + +**Undefined function errors** - If you download and run export_fig and get an error similar to this: +``` +??? Undefined function or method 'print2array' for input arguments of type 'double'. +``` +then you are missing one or more of the files that come in the export_fig package. Make sure that you click the "Get from GitHub" button at the top-right of the download [page](http://www.mathworks.co.uk/matlabcentral/fileexchange/23629-exportfig), then extract all the files in the zip file to the same directory. You should then have all the necessary files. + +### Known issues +There are lots of problems with MATLAB's exporting functions, especially `print`. Export_fig is simply a glorified wrapper for MATLAB's `print` function, and doesn't solve all of its bugs (yet?). Some of the problems I know about are: + +**Fonts** - when using the painters renderer, MATLAB can only export a small number of fonts, details of which can be found [here](http://www.mathworks.com/help/releases/R2014a/matlab/creating_plots/choosing-a-printer-driver.html#f3-96545). Export_fig attempts to correct font names in the resulting EPS file (up to a maximum of 11 different fonts in one figure), but this is not always guaranteed to work. In particular, the text positions will be affected. It also does not work for text blocks where the 'Interpreter' property is set to 'latex'. + +Also, when using the painters renderer, ghostscript will sometimes throw an error such as `Error: /undefined in /findfont`. This suggests that ghostscript could not find a definition file for one of your fonts. One possible fix for this is to make sure the file `EXPORT_FIG_PATH/.ignore/gs_font_path.txt` exists and contains a list of paths to the folder(s) containing the necessary font definitions (make sure that they are TrueType definitions!), separated by a semicolon. + +**RGB color data not yet supported in Painter's mode** - you will see this as a warning if you try to export a figure which contains patch objects whose face or vertex colors are specified as an RGB colour, rather than an index into the colormap, using the painters renderer (the default renderer for vector output). This problem can arise if you use `pcolor`, for example. This is a problem with MATLAB's painters renderer, which also affects `print`; there is currently no fix available in export_fig (other than to export to bitmap). The suggested workaround is to avoid colouring patches using RGB. First, try to use colours in the figure's colourmap (instructions [here](http://www.mathworks.co.uk/support/solutions/en/data/1-6OTPQE/)) - change the colourmap, if necessary. If you are using `pcolor`, try using [uimagesc](http://www.mathworks.com/matlabcentral/fileexchange/11368) (on the file exchange) instead. + +**Dashed contour lines appear solid** - when using the painters renderer, MATLAB cannot generate dashed lines using the `contour` function (either on screen or in exported PDF and EPS files). Details can be found [here](http://www.mathworks.com/support/solutions/en/data/1-14PPHB/?solution=1-14PPHB). + +**Text size** - when using the OpenGL or ZBuffer renderers, large text can be resized relative to the figure when exporting at non-screen-resolution (including using anti-alising at screen resolution). This is a feature of MATLAB's `print `function. In this case, try using the `-painters` option. + +**Lighting and transparency** - when using the painters renderer, transparency and lighting effects are not supported. Sorry, but this is an inherent feature of MATLAB's painters renderer. To find out more about the capabilities of each rendering method, see [here](http://www.mathworks.com/access/helpdesk/help/techdoc/creating_plots/f3-84337.html#f3-102410). You can still export transparent objects to vector format (SVG) using the excellent [plot2svg](http://www.mathworks.com/matlabcentral/fileexchange/7401) package, then convert this to PDF, for example using [Inkscape](http://inkscape.org/). However, it can't handle lighting. + +**Lines in patch objects** - when exporting patch objects to PDF using the painters renderer (default), sometimes the output can appear to have lines across the middle of rectangular patches; these lines are the colour of the background, as if there is a crack in the patch, allowing you to see through. This appears to be due to bugs in MATLAB's internal vector rendering code. These lines can often be removed from the PDF using software such as [InkScape](https://inkscape.org). Sometimes disabling anti-aliasing in the PDF-reader software can get rid of the lines ([discussion](https://github.com/altmany/export_fig/issues/44)). + +**Out of memory** - if you run into memory issues when using export_fig, some ways to get round this are: + 1. Reduce the level of anti-aliasing. + 2. Reduce the size of the figure. + 3. Reduce the export resolution (dpi). + 4. Change the renderer to painters or ZBuffer. + +**Errors** - the other common type of errors people get with export_fig are OpenGL errors. This isn't a fault of export_fig, but either a bug in MATLAB's `print`, or your graphics driver getting itself into a state. Always make sure your graphics driver is up-to-date. If it still doesn't work, try using the ZBuffer renderer. + +### Raising issues +If you think you have found a genuine error or issue with export_fig **that is not listed above**, first ensure that the figure looks correct on screen when rendered using the renderer that export_fig is set to use (e.g. if exporting to PDF or EPS, does the figure look correct on screen using the painters renderer, or if exporting to bitmap, does the figure look correct on screen using the OpenGL renderer?). If it looks wrong then the problem is there, and I cannot help (other than to suggest you try exporting using a different renderer). + +Secondly, if exporting to bitmap, do try all the renderers (i.e. try the options `-opengl`, `-zbuffer` and `-painters` separately), to see if one of them does produce an acceptable output, and if so, use that. + +If this still does not help, then ensure that you are using the latest version of export_fig, which is available [here](https://github.com/altmany/export_fig/archive/master.zip). + +If the figure looks correct on screen, but an error exists in the exported output (which cannot be solved using a different renderer) then please feel free to raise an [issue](https://github.com/altmany/export_fig/issues). Please be sure to include the .fig file, the export_fig command you use, the output you get, and a description of what you expected. I can't promise anything, but if it's easy to fix I may indeed do it. Often I will find that the error is due to a bug in MATLAB's `print` function, in which case I will suggest you submit it as a bug to TheMathWorks, and inform me of any fix they suggest. Also, if there's a feature you'd like that isn't supported please tell me what it is and I'll consider implementing it. + +### And finally... + +![](https://farm4.staticflickr.com/3956/15591911455_b9008bd77e_o_d.jpg) + +If you've ever wondered what's going on in the logo on the export_fig download page (reproduced here), then this explanantion is for you. The logo is designed to demonstrate as many of export_fig's features as possible: + +Given a figure containing a translucent mesh (top right), export_fig can export to pdf (bottom centre), which allows the figure to be zoomed-in without losing quality (because it's a vector graphic), but isn't able to reproduce the translucency. Also, depending on the PDF viewer program, small gaps appear between the patches, which are seen here as thin white lines. + +By contrast, when exporting to png (top left), translucency is preserved (see how the graphic below shows through), and the figure is anti-aliased. However, zooming-in does not reveal more detail since png is a bitmap format. Also, lines appear less sharp than in the pdf output. + diff --git a/tools/plotting/export_fig/append_pdfs.m b/tools/plotting/export_fig/append_pdfs.m new file mode 100644 index 0000000..fe15a6f --- /dev/null +++ b/tools/plotting/export_fig/append_pdfs.m @@ -0,0 +1,124 @@ +%APPEND_PDFS Appends/concatenates multiple PDF files +% +% Example: +% append_pdfs(output, input1, input2, ...) +% append_pdfs(output, input_list{:}) +% append_pdfs test.pdf temp1.pdf temp2.pdf +% +% This function appends multiple PDF files to an existing PDF file, or +% concatenates them into a PDF file if the output file doesn't yet exist. +% +% This function requires that you have ghostscript installed on your +% system. Ghostscript can be downloaded from: http://www.ghostscript.com +% +% IN: +% output - string of output file name (including the extension, .pdf). +% If it exists it is appended to; if not, it is created. +% input1 - string of an input file name (including the extension, .pdf). +% All input files are appended in order. +% input_list - cell array list of input file name strings. All input +% files are appended in order. + +% Copyright: Oliver Woodford, 2011 + +% Thanks to Reinhard Knoll for pointing out that appending multiple pdfs in +% one go is much faster than appending them one at a time. + +% Thanks to Michael Teo for reporting the issue of a too long command line. +% Issue resolved on 5/5/2011, by passing gs a command file. + +% Thanks to Martin Wittmann for pointing out the quality issue when +% appending multiple bitmaps. +% Issue resolved (to best of my ability) 1/6/2011, using the prepress +% setting + +% 26/02/15: If temp dir is not writable, use the output folder for temp +% files when appending (Javier Paredes); sanity check of inputs +% 24/01/18: Fixed error in case of existing output file (append mode) +% 24/01/18: Fixed issue #213: non-ASCII characters in folder names on Windows +% 06/12/18: Avoid an "invalid escape-char" warning upon error + +function append_pdfs(varargin) + + if nargin < 2, return; end % sanity check + + % Are we appending or creating a new file + append = exist(varargin{1}, 'file') == 2; + output = [tempname '.pdf']; + try + % Ensure that the temp dir is writable (Javier Paredes 26/2/15) + fid = fopen(output,'w'); + fwrite(fid,1); + fclose(fid); + delete(output); + isTempDirOk = true; + catch + % Temp dir is not writable, so use the output folder + [dummy,fname,fext] = fileparts(output); %#ok + fpath = fileparts(varargin{1}); + output = fullfile(fpath,[fname fext]); + isTempDirOk = false; + end + if ~append + output = varargin{1}; + varargin = varargin(2:end); + end + + % Create the command file + if isTempDirOk + cmdfile = [tempname '.txt']; + else + cmdfile = fullfile(fpath,[fname '.txt']); + end + prepareCmdFile(cmdfile, output, varargin{:}); + + % Call ghostscript + [status, errMsg] = ghostscript(['@"' cmdfile '"']); + + % Check for ghostscript execution errors + if status && ~isempty(strfind(errMsg,'undefinedfile')) && ispc %#ok + % Fix issue #213: non-ASCII characters in folder names on Windows + for fileIdx = 2 : numel(varargin) + [fpath,fname,fext] = fileparts(varargin{fileIdx}); + varargin{fileIdx} = fullfile(normalizePath(fpath),[fname fext]); + end + % Rerun ghostscript with the normalized folder names + prepareCmdFile(cmdfile, output, varargin{:}); + [status, errMsg] = ghostscript(['@"' cmdfile '"']); + end + + % Delete the command file + delete(cmdfile); + + % Check for ghostscript execution errors + if status + errMsg = strrep(errMsg,'\','\\'); % Avoid an "invalid escape-char" warning + error('YMA:export_fig:append_pdf',errMsg); + end + + % Rename the file if needed + if append + movefile(output, varargin{1}, 'f'); + end +end + +% Prepare a text file with ghostscript directives +function prepareCmdFile(cmdfile, output, varargin) + fh = fopen(cmdfile, 'w'); + fprintf(fh, '-q -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -dPDFSETTINGS=/prepress -sOutputFile="%s" -f', output); + fprintf(fh, ' "%s"', varargin{:}); + fclose(fh); +end + +% Convert long/non-ASCII folder names into their short ASCII equivalents +function pathStr = normalizePath(pathStr) + [fpath,fname,fext] = fileparts(pathStr); + if isempty(fpath) || strcmpi(fpath,pathStr), return, end + dirOutput = evalc(['system(''dir /X /AD "' pathStr '*"'')']); + shortName = strtrim(regexprep(dirOutput,{'.*> *',[fname fext '.*']},'')); + if isempty(shortName) + shortName = [fname fext]; + end + fpath = normalizePath(fpath); %recursive until entire fpath is processed + pathStr = fullfile(fpath, shortName); +end diff --git a/tools/plotting/export_fig/copyfig.m b/tools/plotting/export_fig/copyfig.m old mode 100755 new mode 100644 index 9d38b83..9a5b8cb --- a/tools/plotting/export_fig/copyfig.m +++ b/tools/plotting/export_fig/copyfig.m @@ -1,3 +1,4 @@ +function fh = copyfig(fh) %COPYFIG Create a copy of a figure, without changing the figure % % Examples: @@ -12,22 +13,47 @@ % OUT: % fh_new - The handle of the created figure. -% Copyright (C) Oliver Woodford 2012 +% Copyright (C) Oliver Woodford 2012, Yair Altman 2015 -function fh = copyfig(fh) -% Set the default -if nargin == 0 - fh = gcf; -end -% Is there a legend? -if isempty(findall(fh, 'Type', 'axes', 'Tag', 'legend')) - % Safe to copy using copyobj - fh = copyobj(fh, 0); -else - % copyobj will change the figure, so save and then load it instead - tmp_nam = [tempname '.fig']; - hgsave(fh, tmp_nam); - fh = hgload(tmp_nam); - delete(tmp_nam); +% 26/02/15: If temp dir is not writable, use the dest folder for temp +% destination files (Javier Paredes) +% 15/04/15: Suppress warnings during copyobj (Dun Kirk comment on FEX page 2013-10-02) +% 09/09/18: Fix issue #252: Workaround for cases where copyobj() fails for any reason + + % Set the default + if nargin == 0 + fh = gcf; + end + % Is there a legend? + useCopyobj = isempty(findall(fh, 'Type', 'axes', 'Tag', 'legend')); + if useCopyobj + % Safe to copy using copyobj + oldWarn = warning('off'); %Suppress warnings during copyobj (Dun Kirk comment on FEX page 2013-10-02) + try + fh = copyobj(fh, 0); + catch + % Fix issue #252: Workaround for cases where copyobj() fails for any reason + useCopyobj = false; % if copyobj() croaks, use file save/load below + end + warning(oldWarn); + end + if ~useCopyobj + % copyobj will change the figure, so save and then load it instead + tmp_nam = [tempname '.fig']; + try + % Ensure that the temp dir is writable (Javier Paredes 26/2/15) + fid = fopen(tmp_nam,'w'); + fwrite(fid,1); + fclose(fid); + delete(tmp_nam); % cleanup + catch + % Temp dir is not writable, so use the current folder + [dummy,fname,fext] = fileparts(tmp_nam); %#ok + fpath = pwd; + tmp_nam = fullfile(fpath,[fname fext]); + end + hgsave(fh, tmp_nam); + fh = hgload(tmp_nam); + delete(tmp_nam); + end end -return \ No newline at end of file diff --git a/tools/plotting/export_fig/crop_borders.m b/tools/plotting/export_fig/crop_borders.m new file mode 100644 index 0000000..b87c842 --- /dev/null +++ b/tools/plotting/export_fig/crop_borders.m @@ -0,0 +1,157 @@ +function [A, vA, vB, bb_rel] = crop_borders(A, bcol, padding, crop_amounts) +%CROP_BORDERS Crop the borders of an image or stack of images +% +% [B, vA, vB, bb_rel] = crop_borders(A, bcol, [padding]) +% +%IN: +% A - HxWxCxN stack of images. +% bcol - Cx1 background colour vector. +% padding - scalar indicating how much padding to have in relation to +% the cropped-image-size (0<=padding<=1). Default: 0 +% crop_amounts - 4-element vector of crop amounts: [top,right,bottom,left] +% where NaN/Inf indicate auto-cropping, 0 means no cropping, +% and any other value mean cropping in pixel amounts. +% +%OUT: +% B - JxKxCxN cropped stack of images. +% vA - coordinates in A that contain the cropped image +% vB - coordinates in B where the cropped version of A is placed +% bb_rel - relative bounding box (used for eps-cropping) + +%{ +% 06/03/15: Improved image cropping thanks to Oscar Hartogensis +% 08/06/15: Fixed issue #76: case of transparent figure bgcolor +% 21/02/16: Enabled specifying non-automated crop amounts +% 04/04/16: Fix per Luiz Carvalho for old Matlab releases +% 23/10/16: Fixed issue #175: there used to be a 1px minimal padding in case of crop, now removed +%} + + if nargin < 3 + padding = 0; + end + if nargin < 4 + crop_amounts = nan(1,4); % =auto-cropping + end + crop_amounts(end+1:4) = NaN; % fill missing values with NaN + + [h, w, c, n] = size(A); + if isempty(bcol) % case of transparent bgcolor + bcol = A(ceil(end/2),1,:,1); + end + if isscalar(bcol) + bcol = bcol(ones(c, 1)); + end + + % Crop margin from left + if ~isfinite(crop_amounts(4)) + bail = false; + for l = 1:w + for a = 1:c + if ~all(col(A(:,l,a,:)) == bcol(a)) + bail = true; + break; + end + end + if bail + break; + end + end + else + l = 1 + abs(crop_amounts(4)); + end + + % Crop margin from right + if ~isfinite(crop_amounts(2)) + bcol = A(ceil(end/2),w,:,1); + bail = false; + for r = w:-1:l + for a = 1:c + if ~all(col(A(:,r,a,:)) == bcol(a)) + bail = true; + break; + end + end + if bail + break; + end + end + else + r = w - abs(crop_amounts(2)); + end + + % Crop margin from top + if ~isfinite(crop_amounts(1)) + bcol = A(1,ceil(end/2),:,1); + bail = false; + for t = 1:h + for a = 1:c + if ~all(col(A(t,:,a,:)) == bcol(a)) + bail = true; + break; + end + end + if bail + break; + end + end + else + t = 1 + abs(crop_amounts(1)); + end + + % Crop margin from bottom + bcol = A(h,ceil(end/2),:,1); + if ~isfinite(crop_amounts(3)) + bail = false; + for b = h:-1:t + for a = 1:c + if ~all(col(A(b,:,a,:)) == bcol(a)) + bail = true; + break; + end + end + if bail + break; + end + end + else + b = h - abs(crop_amounts(3)); + end + + if padding == 0 % no padding + % Issue #175: there used to be a 1px minimal padding in case of crop, now removed + %{ + if ~isequal([t b l r], [1 h 1 w]) % Check if we're actually croppping + padding = 1; % Leave one boundary pixel to avoid bleeding on resize + bcol(:) = nan; % make the 1px padding transparent + end + %} + elseif abs(padding) < 1 % pad value is a relative fraction of image size + padding = sign(padding)*round(mean([b-t r-l])*abs(padding)); % ADJUST PADDING + else % pad value is in units of 1/72" points + padding = round(padding); % fix cases of non-integer pad value + end + + if padding > 0 % extra padding + % Create an empty image, containing the background color, that has the + % cropped image size plus the padded border + B = repmat(bcol,[(b-t)+1+padding*2,(r-l)+1+padding*2,1,n]); % Fix per Luiz Carvalho + % vA - coordinates in A that contain the cropped image + vA = [t b l r]; + % vB - coordinates in B where the cropped version of A will be placed + vB = [padding+1, (b-t)+1+padding, padding+1, (r-l)+1+padding]; + % Place the original image in the empty image + B(vB(1):vB(2), vB(3):vB(4), :, :) = A(vA(1):vA(2), vA(3):vA(4), :, :); + A = B; + else % extra cropping + vA = [t-padding b+padding l-padding r+padding]; + A = A(vA(1):vA(2), vA(3):vA(4), :, :); + vB = [NaN NaN NaN NaN]; + end + + % For EPS cropping, determine the relative BoundingBox - bb_rel + bb_rel = [l-1 h-b-1 r+1 h-t+1]./[w h w h]; +end + +function A = col(A) + A = A(:); +end diff --git a/tools/plotting/export_fig/eps2pdf.m b/tools/plotting/export_fig/eps2pdf.m old mode 100755 new mode 100644 index a4bb6c8..e8379b8 --- a/tools/plotting/export_fig/eps2pdf.m +++ b/tools/plotting/export_fig/eps2pdf.m @@ -1,3 +1,4 @@ +function eps2pdf(source, dest, crop, append, gray, quality, gs_options) %EPS2PDF Convert an eps file to pdf format using ghostscript % % Examples: @@ -6,6 +7,7 @@ % eps2pdf(source, dest, crop, append) % eps2pdf(source, dest, crop, append, gray) % eps2pdf(source, dest, crop, append, gray, quality) +% eps2pdf(source, dest, crop, append, gray, quality, gs_options) % % This function converts an eps file to pdf format. The output can be % optionally cropped and also converted to grayscale. If the output pdf @@ -16,23 +18,25 @@ % This function requires that you have ghostscript installed on your % system. Ghostscript can be downloaded from: http://www.ghostscript.com % -%IN: -% source - filename of the source eps file to convert. The filename is -% assumed to already have the extension ".eps". -% dest - filename of the destination pdf file. The filename is assumed to -% already have the extension ".pdf". -% crop - boolean indicating whether to crop the borders off the pdf. -% Default: true. -% append - boolean indicating whether the eps should be appended to the -% end of the pdf as a new page (if the pdf exists already). -% Default: false. -% gray - boolean indicating whether the output pdf should be grayscale or -% not. Default: false. +% Inputs: +% source - filename of the source eps file to convert. The filename is +% assumed to already have the extension ".eps". +% dest - filename of the destination pdf file. The filename is assumed +% to already have the extension ".pdf". +% crop - boolean indicating whether to crop the borders off the pdf. +% Default: true. +% append - boolean indicating whether the eps should be appended to the +% end of the pdf as a new page (if the pdf exists already). +% Default: false. +% gray - boolean indicating whether the output pdf should be grayscale +% or not. Default: false. % quality - scalar indicating the level of image bitmap quality to % output. A larger value gives a higher quality. quality > 100 % gives lossless output. Default: ghostscript prepress default. +% gs_options - optional ghostscript options (e.g.: '-dNoOutputFonts'). If +% multiple options are needed, enclose in call array: {'-a','-b'} -% Copyright (C) Oliver Woodford 2009-2011 +% Copyright (C) Oliver Woodford 2009-2014, Yair Altman 2015- % Suggestion of appending pdf files provided by Matt C at: % http://www.mathworks.com/matlabcentral/fileexchange/23629 @@ -42,93 +46,155 @@ % Thank you to Scott for pointing out the subsampling of very small images, % which was fixed for lossless compression settings. -% 9/12/2011 Pass font path to ghostscript. +% 09/12/11: Pass font path to ghostscript +% 26/02/15: If temp dir is not writable, use the dest folder for temp +% destination files (Javier Paredes) +% 28/02/15: Enable users to specify optional ghostscript options (issue #36) +% 01/03/15: Upon GS error, retry without the -sFONTPATH= option (this might solve +% some /findfont errors according to James Rankin, FEX Comment 23/01/15) +% 23/06/15: Added extra debug info in case of ghostscript error; code indentation +% 04/10/15: Suggest a workaround for issue #41 (missing font path; thanks Mariia Fedotenkova) +% 22/02/16: Bug fix from latest release of this file (workaround for issue #41) +% 20/03/17: Added informational message in case of GS croak (issue #186) +% 16/01/18: Improved appending of multiple EPS files into single PDF (issue #233; thanks @shartjen) -function eps2pdf(source, dest, crop, append, gray, quality) -% Intialise the options string for ghostscript -options = ['-q -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -dPDFSETTINGS=/prepress -sOutputFile="' dest '"']; -% Set crop option -if nargin < 3 || crop - options = [options ' -dEPSCrop']; -end -% Set the font path -fp = font_path(); -if ~isempty(fp) - options = [options ' -sFONTPATH="' fp '"']; -end -% Set the grayscale option -if nargin > 4 && gray - options = [options ' -sColorConversionStrategy=Gray -dProcessColorModel=/DeviceGray']; -end -% Set the bitmap quality -if nargin > 5 && ~isempty(quality) - options = [options ' -dAutoFilterColorImages=false -dAutoFilterGrayImages=false']; - if quality > 100 - options = [options ' -dColorImageFilter=/FlateEncode -dGrayImageFilter=/FlateEncode -c ".setpdfwrite << /ColorImageDownsampleThreshold 10 /GrayImageDownsampleThreshold 10 >> setdistillerparams"']; - else - options = [options ' -dColorImageFilter=/DCTEncode -dGrayImageFilter=/DCTEncode']; - v = 1 + (quality < 80); - quality = 1 - quality / 100; - s = sprintf('<< /QFactor %.2f /Blend 1 /HSample [%d 1 1 %d] /VSample [%d 1 1 %d] >>', quality, v, v, v, v); - options = sprintf('%s -c ".setpdfwrite << /ColorImageDict %s /GrayImageDict %s >> setdistillerparams"', options, s, s); + % Intialise the options string for ghostscript + options = ['-q -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -dPDFSETTINGS=/prepress -sOutputFile="' dest '"']; + % Set crop option + if nargin < 3 || crop + options = [options ' -dEPSCrop']; end -end -% Check if the output file exists -if nargin > 3 && append && exist(dest, 'file') == 2 - % File exists - append current figure to the end - tmp_nam = tempname; - % Copy the file - copyfile(dest, tmp_nam); - % Add the output file names - options = [options ' -f "' tmp_nam '" "' source '"']; - try - % Convert to pdf using ghostscript - [status message] = ghostscript(options); - catch - % Delete the intermediate file - delete(tmp_nam); - rethrow(lasterror); + % Set the font path + fp = font_path(); + if ~isempty(fp) + options = [options ' -sFONTPATH="' fp '"']; end - % Delete the intermediate file - delete(tmp_nam); -else - % File doesn't exist or should be over-written - % Add the output file names - options = [options ' -f "' source '"']; - % Convert to pdf using ghostscript - [status message] = ghostscript(options); -end -% Check for error -if status - % Report error - if isempty(message) - error('Unable to generate pdf. Check destination directory is writable.'); + % Set the grayscale option + if nargin > 4 && gray + options = [options ' -sColorConversionStrategy=Gray -dProcessColorModel=/DeviceGray']; + end + % Set the bitmap quality + if nargin > 5 && ~isempty(quality) + options = [options ' -dAutoFilterColorImages=false -dAutoFilterGrayImages=false']; + if quality > 100 + options = [options ' -dColorImageFilter=/FlateEncode -dGrayImageFilter=/FlateEncode -c ".setpdfwrite << /ColorImageDownsampleThreshold 10 /GrayImageDownsampleThreshold 10 >> setdistillerparams"']; + else + options = [options ' -dColorImageFilter=/DCTEncode -dGrayImageFilter=/DCTEncode']; + v = 1 + (quality < 80); + quality = 1 - quality / 100; + s = sprintf('<< /QFactor %.2f /Blend 1 /HSample [%d 1 1 %d] /VSample [%d 1 1 %d] >>', quality, v, v, v, v); + options = sprintf('%s -c ".setpdfwrite << /ColorImageDict %s /GrayImageDict %s >> setdistillerparams"', options, s, s); + end + end + % Enable users to specify optional ghostscript options (issue #36) + if nargin > 6 && ~isempty(gs_options) + if iscell(gs_options) + gs_options = sprintf(' %s',gs_options{:}); + elseif ~ischar(gs_options) + error('gs_options input argument must be a string or cell-array of strings'); + else + gs_options = [' ' gs_options]; + end + options = [options gs_options]; + end + % Check if the output file exists + if nargin > 3 && append && exist(dest, 'file') == 2 + % File exists - append current figure to the end + tmp_nam = [tempname '.pdf']; + [fpath,fname,fext] = fileparts(tmp_nam); + try + % Ensure that the temp dir is writable (Javier Paredes 26/2/15) + fid = fopen(tmp_nam,'w'); + fwrite(fid,1); + fclose(fid); + delete(tmp_nam); + catch + % Temp dir is not writable, so use the dest folder + fpath = fileparts(dest); + tmp_nam = fullfile(fpath,[fname fext]); + end + % Copy the existing (dest) pdf file to temporary folder + copyfile(dest, tmp_nam); + % Produce an interim pdf of the source eps, rather than adding the eps directly (issue #233) + ghostscript([options ' -f "' source '"']); + [~,fname] = fileparts(tempname); + tmp_nam2 = fullfile(fpath,[fname fext]); % ensure using a writable folder (not necessarily tempdir) + copyfile(dest, tmp_nam2); + % Add the existing pdf and interim pdf as inputs to ghostscript + %options = [options ' -f "' tmp_nam '" "' source '"']; % append the source eps to dest pdf + options = [options ' -f "' tmp_nam '" "' tmp_nam2 '"']; % append the interim pdf to dest pdf + try + % Convert to pdf using ghostscript + [status, message] = ghostscript(options); + catch me + % Delete the intermediate files and rethrow the error + delete(tmp_nam); + delete(tmp_nam2); + rethrow(me); + end + % Delete the intermediate (temporary) files + delete(tmp_nam); + delete(tmp_nam2); else - error(message); + % File doesn't exist or should be over-written + % Add the source eps file as input to ghostscript + options = [options ' -f "' source '"']; + % Convert to pdf using ghostscript + [status, message] = ghostscript(options); + end + % Check for error + if status + % Retry without the -sFONTPATH= option (this might solve some GS + % /findfont errors according to James Rankin, FEX Comment 23/01/15) + orig_options = options; + if ~isempty(fp) + options = regexprep(options, ' -sFONTPATH=[^ ]+ ',' '); + status = ghostscript(options); + if ~status, return; end % hurray! (no error) + end + % Report error + if isempty(message) + error('Unable to generate pdf. Check destination directory is writable.'); + elseif ~isempty(strfind(message,'/typecheck in /findfont')) + % Suggest a workaround for issue #41 (missing font path) + font_name = strtrim(regexprep(message,'.*Operand stack:\s*(.*)\s*Execution.*','$1')); + fprintf(2, 'Ghostscript error: could not find the following font(s): %s\n', font_name); + fpath = fileparts(mfilename('fullpath')); + gs_fonts_file = fullfile(fpath, '.ignore', 'gs_font_path.txt'); + fprintf(2, ' try to add the font''s folder to your %s file\n\n', gs_fonts_file); + error('export_fig error'); + else + fprintf(2, '\nGhostscript error: perhaps %s is open by another application\n', dest); + if ~isempty(gs_options) + fprintf(2, ' or maybe the%s option(s) are not accepted by your GS version\n', gs_options); + end + fprintf(2, ' or maybe you have another gs executable in your system''s path\n'); + fprintf(2, 'Ghostscript options: %s\n\n', orig_options); + error(message); + end end end -return % Function to return (and create, where necessary) the font path function fp = font_path() -fp = user_string('gs_font_path'); -if ~isempty(fp) - return -end -% Create the path -% Start with the default path -fp = getenv('GS_FONTPATH'); -% Add on the typical directories for a given OS -if ispc + fp = user_string('gs_font_path'); if ~isempty(fp) - fp = [fp ';']; + return end - fp = [fp getenv('WINDIR') filesep 'Fonts']; -else - if ~isempty(fp) - fp = [fp ':']; + % Create the path + % Start with the default path + fp = getenv('GS_FONTPATH'); + % Add on the typical directories for a given OS + if ispc + if ~isempty(fp) + fp = [fp ';']; + end + fp = [fp getenv('WINDIR') filesep 'Fonts']; + else + if ~isempty(fp) + fp = [fp ':']; + end + fp = [fp '/usr/share/fonts:/usr/local/share/fonts:/usr/share/fonts/X11:/usr/local/share/fonts/X11:/usr/share/fonts/truetype:/usr/local/share/fonts/truetype']; end - fp = [fp '/usr/share/fonts:/usr/local/share/fonts:/usr/share/fonts/X11:/usr/local/share/fonts/X11:/usr/share/fonts/truetype:/usr/local/share/fonts/truetype']; + user_string('gs_font_path', fp); end -user_string('gs_font_path', fp); -return diff --git a/tools/plotting/export_fig/export_fig.m b/tools/plotting/export_fig/export_fig.m old mode 100755 new mode 100644 index ca777f6..253b679 --- a/tools/plotting/export_fig/export_fig.m +++ b/tools/plotting/export_fig/export_fig.m @@ -1,40 +1,52 @@ -%EXPORT_FIG Exports figures suitable for publication +function [imageData, alpha] = export_fig(varargin) %#ok<*STRCL1> +%EXPORT_FIG Exports figures in a publication-quality format % % Examples: -% im = export_fig -% [im alpha] = export_fig +% imageData = export_fig +% [imageData, alpha] = export_fig % export_fig filename % export_fig filename -format1 -format2 % export_fig ... -nocrop +% export_fig ... -c[,,,] % export_fig ... -transparent % export_fig ... -native % export_fig ... -m % export_fig ... -r % export_fig ... -a % export_fig ... -q +% export_fig ... -p +% export_fig ... -d +% export_fig ... -depsc % export_fig ... - % export_fig ... - % export_fig ... -append % export_fig ... -bookmark +% export_fig ... -clipboard +% export_fig ... -update +% export_fig ... -nofontswap +% export_fig ... -font_space +% export_fig ... -linecaps +% export_fig ... -noinvert % export_fig(..., handle) % % This function saves a figure or single axes to one or more vector and/or -% bitmap file formats, and/or outputs a rasterized version to the -% workspace, with the following properties: +% bitmap file formats, and/or outputs a rasterized version to the workspace, +% with the following properties: % - Figure/axes reproduced as it appears on screen % - Cropped borders (optional) % - Embedded fonts (vector formats) % - Improved line and grid line styles % - Anti-aliased graphics (bitmap formats) % - Render images at native resolution (optional for bitmap formats) -% - Transparent background supported (pdf, eps, png) -% - Semi-transparent patch objects supported (png only) +% - Transparent background supported (pdf, eps, png, tiff) +% - Semi-transparent patch objects supported (png, tiff) % - RGB, CMYK or grayscale output (CMYK only with pdf, eps, tiff) % - Variable image compression, including lossless (pdf, eps, jpg) +% - Optional rounded line-caps (pdf, eps) % - Optionally append to file (pdf, tiff) % - Vector formats: pdf, eps -% - Bitmap formats: png, tiff, jpg, bmp, export to workspace -% +% - Bitmap formats: png, tiff, jpg, bmp, export to workspace +% % This function is especially suited to exporting figures for use in % publications and presentations, because of the high quality and % portability of media produced. @@ -44,29 +56,27 @@ % output file. For transparent background (and semi-transparent patch % objects), use the -transparent option or set the figure 'Color' property % to 'none'. To make axes transparent set the axes 'Color' property to -% 'none'. Pdf, eps and png are the only file formats to support a -% transparent background, whilst the png format alone supports transparency -% of patch objects. +% 'none'. PDF, EPS, TIF & PNG are the only formats that support a transparent +% background; only TIF & PNG formats support transparency of patch objects. % % The choice of renderer (opengl, zbuffer or painters) has a large impact -% on the quality of output. Whilst the default value (opengl for bitmaps, -% painters for vector formats) generally gives good results, if you aren't -% satisfied then try another renderer. Notes: 1) For vector formats (eps, -% pdf), only painters generates vector graphics. 2) For bitmaps, only +% on the quality of output. The default value (opengl for bitmaps, painters +% for vector formats) generally gives good results, but if you aren't +% satisfied then try another renderer. Notes: 1) For vector formats (EPS, +% PDF), only painters generates vector graphics. 2) For bitmaps, only % opengl can render transparent patch objects correctly. 3) For bitmaps, % only painters will correctly scale line dash and dot lengths when % magnifying or anti-aliasing. 4) Fonts may be substitued with Courier when % using painters. % -% When exporting to vector format (pdf & eps) and bitmap format using the +% When exporting to vector format (PDF & EPS) and bitmap format using the % painters renderer, this function requires that ghostscript is installed % on your system. You can download this from: % http://www.ghostscript.com % When exporting to eps it additionally requires pdftops, from the Xpdf -% suite of functions. You can download this from: -% http://www.foolabs.com/xpdf +% suite of functions. You can download this from: http://xpdfreader.com % -%IN: +% Inputs: % filename - string containing the name (optionally including full or % relative path) of the file the figure is to be saved as. If % a path is not specified, the figure is saved in the current @@ -81,11 +91,15 @@ % of formats are valid. % -nocrop - option indicating that the borders of the output are not to % be cropped. +% -c[,,,] - option indicating crop amounts. Must be +% a 4-element vector of numeric values: [top,right,bottom,left] +% where NaN/Inf indicate auto-cropping, 0 means no cropping, +% and any other value mean cropping in pixel amounts. % -transparent - option indicating that the figure background is to be -% made transparent (png, pdf and eps output only). +% made transparent (png, pdf, tif and eps output only). % -m - option where val indicates the factor to magnify the % on-screen figure pixel dimensions by when generating bitmap -% outputs. Default: '-m1'. +% outputs (does not affect vector formats). Default: '-m1'. % -r - option val indicates the resolution (in pixels per inch) to % export bitmap and vector outputs at, keeping the dimensions % of the on-screen figure. Default: '-r864' (for vector output @@ -104,9 +118,10 @@ % -a1, -a2, -a3, -a4 - option indicating the amount of anti-aliasing to % use for bitmap outputs. '-a1' means no anti- % aliasing; '-a4' is the maximum amount (default). -% - - option to force a particular renderer (painters, opengl -% or zbuffer) to be used over the default: opengl for -% bitmaps; painters for vector formats. +% - - option to force a particular renderer (painters, opengl or +% zbuffer). Default value: opengl for bitmap formats or +% figures with patches and/or transparent annotations; +% painters for vector formats without patches/transparencies. % - - option indicating which colorspace color figures should % be saved in: RGB (default), CMYK or gray. CMYK is only % supported in pdf, eps and tiff output. @@ -117,26 +132,50 @@ % default for pdf & eps. Note: lossless compression can % sometimes give a smaller file size than the default lossy % compression, depending on the type of images. +% -p - option to pad a border of width val to exported files, where +% val is either a relative size with respect to cropped image +% size (i.e. p=0.01 adds a 1% border). For EPS & PDF formats, +% val can also be integer in units of 1/72" points (abs(val)>1). +% val can be positive (padding) or negative (extra cropping). +% If used, the -nocrop flag will be ignored, i.e. the image will +% always be cropped and then padded. Default: 0 (i.e. no padding). % -append - option indicating that if the file (pdfs only) already % exists, the figure is to be appended as a new page, instead % of being overwritten (default). % -bookmark - option to indicate that a bookmark with the name of the % figure is to be created in the output file (pdf only). -% handle - The handle of the figure, axes or uipanels (can be an array of -% handles, but the objects must be in the same figure) to be -% saved. Default: gcf. +% -clipboard - option to save output as an image on the system clipboard. +% Note: background transparency is not preserved in clipboard +% -d - option to indicate a ghostscript setting. For example, +% -dMaxBitmap=0 or -dNoOutputFonts (Ghostscript 9.15+). +% -depsc - option to use EPS level-3 rather than the default level-2 print +% device. This solves some bugs with Matlab's default -depsc2 device +% such as discolored subplot lines on images (vector formats only). +% -update - option to download and install the latest version of export_fig +% -nofontswap - option to avoid font swapping. Font swapping is automatically +% done in vector formats (only): 11 standard Matlab fonts are +% replaced by the original figure fonts. This option prevents this. +% -font_space - option to set a spacer character for font-names that +% contain spaces, used by EPS/PDF. Default: '' +% -linecaps - option to create rounded line-caps (vector formats only). +% -noinvert - option to avoid setting figure's InvertHardcopy property to +% 'off' during output (this solves some problems of empty outputs). +% handle - The handle of the figure, axes or uipanels (can be an array of +% handles, but the objects must be in the same figure) to be +% saved. Default: gcf. % -%OUT: -% im - MxNxC uint8 image array of the figure. -% alpha - MxN single array of alphamatte values in range [0,1], for the -% case when the background is transparent. +% Outputs: +% imageData - MxNxC uint8 image array of the exported image. +% alpha - MxN single array of alphamatte values in the range [0,1], +% for the case when the background is transparent. % % Some helpful examples and tips can be found at: -% http://sites.google.com/site/oliverwoodford/software/export_fig +% https://github.com/altmany/export_fig % -% See also PRINT, SAVEAS. +% See also PRINT, SAVEAS, ScreenCapture (on the Matlab File Exchange) -% Copyright (C) Oliver Woodford 2008-2012 +%{ +% Copyright (C) Oliver Woodford 2008-2014, Yair Altman 2015- % The idea of using ghostscript is inspired by Peder Axensten's SAVEFIG % (fex id: 10889) which is itself inspired by EPS2PDF (fex id: 5782). @@ -157,670 +196,1285 @@ % fix anyway. % Thanks to Tammy Threadgill for reporting a bug where an axes is not % isolated from gui objects. - +%} +%{ % 23/02/12: Ensure that axes limits don't change during printing -% 14/03/12: Fix bug in fixing the axes limits (thanks to Tobias Lamour for -% reporting it). -% 02/05/12: Incorporate patch of Petr Nechaev (many thanks), enabling -% bookmarking of figures in pdf files. -% 09/05/12: Incorporate patch of Arcelia Arrieta (many thanks), to keep -% tick marks fixed. -% 12/12/12: Add support for isolating uipanels. Thanks to michael for -% suggesting it. -% 25/09/13: Add support for changing resolution in vector formats. Thanks -% to Jan Jaap Meijer for suggesting it. - -function [im, alpha] = export_fig(varargin) -% Make sure the figure is rendered correctly _now_ so that properties like -% axes limits are up-to-date. -drawnow; -% Parse the input arguments -[fig, options] = parse_args(nargout, varargin{:}); -% Isolate the subplot, if it is one -cls = all(ismember(get(fig, 'Type'), {'axes', 'uipanel'})); -if cls - % Given handles of one or more axes, so isolate them from the rest - fig = isolate_axes(fig); -else - % Check we have a figure - if ~isequal(get(fig, 'Type'), 'figure'); - error('Handle must be that of a figure, axes or uipanel'); - end - % Get the old InvertHardcopy mode - old_mode = get(fig, 'InvertHardcopy'); -end -% Hack the font units where necessary (due to a font rendering bug in -% print?). This may not work perfectly in all cases. Also it can change the -% figure layout if reverted, so use a copy. -magnify = options.magnify * options.aa_factor; -if isbitmap(options) && magnify ~= 1 - fontu = findobj(fig, 'FontUnits', 'normalized'); - if ~isempty(fontu) - % Some normalized font units found - if ~cls - fig = copyfig(fig); - set(fig, 'Visible', 'off'); - fontu = findobj(fig, 'FontUnits', 'normalized'); - cls = true; - end - set(fontu, 'FontUnits', 'points'); +% 14/03/12: Fix bug in fixing the axes limits (thanks to Tobias Lamour for reporting it). +% 02/05/12: Incorporate patch of Petr Nechaev (many thanks), enabling bookmarking of figures in pdf files. +% 09/05/12: Incorporate patch of Arcelia Arrieta (many thanks), to keep tick marks fixed. +% 12/12/12: Add support for isolating uipanels. Thanks to michael for suggesting it. +% 25/09/13: Add support for changing resolution in vector formats. Thanks to Jan Jaap Meijer for suggesting it. +% 07/05/14: Add support for '~' at start of path. Thanks to Sally Warner for suggesting it. +% 24/02/15: Fix Matlab R2014b bug (issue #34): plot markers are not displayed when ZLimMode='manual' +% 25/02/15: Fix issue #4 (using HG2 on R2014a and earlier) +% 25/02/15: Fix issue #21 (bold TeX axes labels/titles in R2014b) +% 26/02/15: If temp dir is not writable, use the user-specified folder for temporary EPS/PDF files (Javier Paredes) +% 27/02/15: Modified repository URL from github.com/ojwoodford to /altmany +% Indented main function +% Added top-level try-catch block to display useful workarounds +% 28/02/15: Enable users to specify optional ghostscript options (issue #36) +% 06/03/15: Improved image padding & cropping thanks to Oscar Hartogensis +% 26/03/15: Fixed issue #49 (bug with transparent grayscale images); fixed out-of-memory issue +% 26/03/15: Fixed issue #42: non-normalized annotations on HG1 +% 26/03/15: Fixed issue #46: Ghostscript crash if figure units <> pixels +% 27/03/15: Fixed issue #39: bad export of transparent annotations/patches +% 28/03/15: Fixed issue #50: error on some Matlab versions with the fix for issue #42 +% 29/03/15: Fixed issue #33: bugs in Matlab's print() function with -cmyk +% 29/03/15: Improved processing of input args (accept space between param name & value, related to issue #51) +% 30/03/15: When exporting *.fig files, then saveas *.fig if figure is open, otherwise export the specified fig file +% 30/03/15: Fixed edge case bug introduced yesterday (commit #ae1755bd2e11dc4e99b95a7681f6e211b3fa9358) +% 09/04/15: Consolidated header comment sections; initialize output vars only if requested (nargout>0) +% 14/04/15: Workaround for issue #45: lines in image subplots are exported in invalid color +% 15/04/15: Fixed edge-case in parsing input parameters; fixed help section to show the -depsc option (issue #45) +% 21/04/15: Bug fix: Ghostscript croaks on % chars in output PDF file (reported by Sven on FEX page, 15-Jul-2014) +% 22/04/15: Bug fix: Pdftops croaks on relative paths (reported by Tintin Milou on FEX page, 19-Jan-2015) +% 04/05/15: Merged fix #63 (Kevin Mattheus Moerman): prevent tick-label changes during export +% 07/05/15: Partial fix for issue #65: PDF export used painters rather than opengl renderer (thanks Nguyenr) +% 08/05/15: Fixed issue #65: bad PDF append since commit #e9f3cdf 21/04/15 (thanks Robert Nguyen) +% 12/05/15: Fixed issue #67: exponent labels cropped in export, since fix #63 (04/05/15) +% 28/05/15: Fixed issue #69: set non-bold label font only if the string contains symbols (\beta etc.), followup to issue #21 +% 29/05/15: Added informative error message in case user requested SVG output (issue #72) +% 09/06/15: Fixed issue #58: -transparent removed anti-aliasing when exporting to PNG +% 19/06/15: Added -update option to download and install the latest version of export_fig +% 07/07/15: Added -nofontswap option to avoid font-swapping in EPS/PDF +% 16/07/15: Fixed problem with anti-aliasing on old Matlab releases +% 11/09/15: Fixed issue #103: magnification must never become negative; also fixed reported error msg in parsing input params +% 26/09/15: Alert if trying to export transparent patches/areas to non-PNG outputs (issue #108) +% 04/10/15: Do not suggest workarounds for certain errors that have already been handled previously +% 01/11/15: Fixed issue #112: use same renderer in print2eps as export_fig (thanks to Jesús Pestana Puerta) +% 10/11/15: Custom GS installation webpage for MacOS. Thanks to Andy Hueni via FEX +% 19/11/15: Fixed clipboard export in R2015b (thanks to Dan K via FEX) +% 21/02/16: Added -c option for indicating specific crop amounts (idea by Cedric Noordam on FEX) +% 08/05/16: Added message about possible error reason when groot.Units~=pixels (issue #149) +% 17/05/16: Fixed case of image YData containing more than 2 elements (issue #151) +% 08/08/16: Enabled exporting transparency to TIF, in addition to PNG/PDF (issue #168) +% 11/12/16: Added alert in case of error creating output PDF/EPS file (issue #179) +% 13/12/16: Minor fix to the commit for issue #179 from 2 days ago +% 22/03/17: Fixed issue #187: only set manual ticks when no exponent is present +% 09/04/17: Added -linecaps option (idea by Baron Finer, issue #192) +% 15/09/17: Fixed issue #205: incorrect tick-labels when Ticks number don't match the TickLabels number +% 15/09/17: Fixed issue #210: initialize alpha map to ones instead of zeros when -transparent is not used +% 18/09/17: Added -font_space option to replace font-name spaces in EPS/PDF (workaround for issue #194) +% 18/09/17: Added -noinvert option to solve some export problems with some graphic cards (workaround for issue #197) +% 08/11/17: Fixed issue #220: axes exponent is removed in HG1 when TickMode is 'manual' (internal Matlab bug) +% 08/11/17: Fixed issue #221: alert if the requested folder does not exist +% 19/11/17: Workaround for issue #207: alert when trying to use transparent bgcolor with -opengl +% 29/11/17: Workaround for issue #206: warn if exporting PDF/EPS for a figure that contains an image +% 11/12/17: Fixed issue #230: use OpenGL renderer when exported image contains transparency (also see issue #206) +% 30/01/18: Updated SVG message to point to https://github.com/kupiqu/plot2svg and display user-selected filename if available +% 27/02/18: Fixed issue #236: axes exponent cropped from output if on right-hand axes +% 29/05/18: Fixed issue #245: process "string" inputs just like 'char' inputs +% 13/08/18: Fixed issue #249: correct black axes color to off-black to avoid extra cropping with -transparent +% 27/08/18: Added a possible file-open reason in EPS/PDF write-error message (suggested by "craq" on FEX page) +% 22/09/18: Xpdf website changed to xpdfreader.com +% 23/09/18: Fixed issue #243: only set non-bold font (workaround for issue #69) in R2015b or earlier; warn if changing font +% 23/09/18: Workaround for issue #241: don't use -r864 in EPS/PDF outputs when -native is requested (solves black lines problem) +% 18/11/18: Issue #261: Added informative alert when trying to export a uifigure (which is not currently supported) +% 13/12/18: Issue #261: Fixed last commit for cases of specifying axes/panel handle as input, rather than a figure handle +%} + + if nargout + [imageData, alpha] = deal([]); end -end -% MATLAB "feature": axes limits and tick marks can change when printing -Hlims = findall(fig, 'Type', 'axes'); -if ~cls - % Record the old axes limit and tick modes - Xlims = make_cell(get(Hlims, 'XLimMode')); - Ylims = make_cell(get(Hlims, 'YLimMode')); - Zlims = make_cell(get(Hlims, 'ZLimMode')); - Xtick = make_cell(get(Hlims, 'XTickMode')); - Ytick = make_cell(get(Hlims, 'YTickMode')); - Ztick = make_cell(get(Hlims, 'ZTickMode')); -end -% Set all axes limit and tick modes to manual, so the limits and ticks can't change -set(Hlims, 'XLimMode', 'manual', 'YLimMode', 'manual', 'ZLimMode', 'manual', 'XTickMode', 'manual', 'YTickMode', 'manual', 'ZTickMode', 'manual'); -% Set to print exactly what is there -set(fig, 'InvertHardcopy', 'off'); -% Set the renderer -switch options.renderer - case 1 - renderer = '-opengl'; - case 2 - renderer = '-zbuffer'; - case 3 - renderer = '-painters'; - otherwise - renderer = '-opengl'; % Default for bitmaps -end -% Do the bitmap formats first -if isbitmap(options) - % Get the background colour - if options.transparent && (options.png || options.alpha) - % Get out an alpha channel - % MATLAB "feature": black colorbar axes can change to white and vice versa! - hCB = findobj(fig, 'Type', 'axes', 'Tag', 'Colorbar'); - if isempty(hCB) - yCol = []; - xCol = []; - else - yCol = get(hCB, 'YColor'); - xCol = get(hCB, 'XColor'); - if iscell(yCol) - yCol = cell2mat(yCol); - xCol = cell2mat(xCol); - end - yCol = sum(yCol, 2); - xCol = sum(xCol, 2); - end - % MATLAB "feature": apparently figure size can change when changing - % colour in -nodisplay mode - pos = get(fig, 'Position'); - % Set the background colour to black, and set size in case it was - % changed internally - tcol = get(fig, 'Color'); - set(fig, 'Color', 'k', 'Position', pos); - % Correct the colorbar axes colours - set(hCB(yCol==0), 'YColor', [0 0 0]); - set(hCB(xCol==0), 'XColor', [0 0 0]); - % Print large version to array - B = print2array(fig, magnify, renderer); - % Downscale the image - B = downsize(single(B), options.aa_factor); - % Set background to white (and set size) - set(fig, 'Color', 'w', 'Position', pos); - % Correct the colorbar axes colours - set(hCB(yCol==3), 'YColor', [1 1 1]); - set(hCB(xCol==3), 'XColor', [1 1 1]); - % Print large version to array - A = print2array(fig, magnify, renderer); - % Downscale the image - A = downsize(single(A), options.aa_factor); - % Set the background colour (and size) back to normal - set(fig, 'Color', tcol, 'Position', pos); - % Compute the alpha map - alpha = round(sum(B - A, 3)) / (255 * 3) + 1; - A = alpha; - A(A==0) = 1; - A = B ./ A(:,:,[1 1 1]); - clear B - % Convert to greyscale - if options.colourspace == 2 - A = rgb2grey(A); - end - A = uint8(A); - % Crop the background - if options.crop - [alpha, v] = crop_background(alpha, 0); - A = A(v(1):v(2),v(3):v(4),:); - end - if options.png - % Compute the resolution - res = options.magnify * get(0, 'ScreenPixelsPerInch') / 25.4e-3; - % Save the png - imwrite(A, [options.name '.png'], 'Alpha', double(alpha), 'ResolutionUnit', 'meter', 'XResolution', res, 'YResolution', res); - % Clear the png bit - options.png = false; - end - % Return only one channel for greyscale - if isbitmap(options) - A = check_greyscale(A); - end - if options.alpha - % Store the image - im = A; - % Clear the alpha bit - options.alpha = false; - end - % Get the non-alpha image - if isbitmap(options) - alph = alpha(:,:,ones(1, size(A, 3))); - A = uint8(single(A) .* alph + 255 * (1 - alph)); - clear alph - end - if options.im - % Store the new image - im = A; - end + hadError = false; + displaySuggestedWorkarounds = true; + + % Ensure the figure is rendered correctly _now_ so that properties like axes limits are up-to-date + drawnow; + pause(0.05); % this solves timing issues with Java Swing's EDT (http://undocumentedmatlab.com/blog/solving-a-matlab-hang-problem) + + % Parse the input arguments + fig = get(0, 'CurrentFigure'); + [fig, options] = parse_args(nargout, fig, varargin{:}); + + % Ensure that we have a figure handle + if isequal(fig,-1) + return; % silent bail-out + elseif isempty(fig) + error('No figure found'); else - % Print large version to array - if options.transparent - % MATLAB "feature": apparently figure size can change when changing - % colour in -nodisplay mode - pos = get(fig, 'Position'); - tcol = get(fig, 'Color'); - set(fig, 'Color', 'w', 'Position', pos); - A = print2array(fig, magnify, renderer); - set(fig, 'Color', tcol, 'Position', pos); - tcol = 255; - else - [A, tcol] = print2array(fig, magnify, renderer); - end - % Crop the background - if options.crop - A = crop_background(A, tcol); - end - % Downscale the image - A = downsize(A, options.aa_factor); - if options.colourspace == 2 - % Convert to greyscale - A = rgb2grey(A); - else - % Return only one channel for greyscale - A = check_greyscale(A); - end - % Outputs - if options.im - im = A; - end - if options.alpha - im = A; - alpha = zeros(size(A, 1), size(A, 2), 'single'); + oldWarn = warning('off','MATLAB:HandleGraphics:ObsoletedProperty:JavaFrame'); + try jf = get(handle(ancestor(fig,'figure')),'JavaFrame'); catch, jf=1; end + warning(oldWarn); + if isempty(jf) + error('Figures created using the uifigure command or App Designer are not supported by export_fig. See issue #261 for details.'); end end - % Save the images - if options.png - res = options.magnify * get(0, 'ScreenPixelsPerInch') / 25.4e-3; - imwrite(A, [options.name '.png'], 'ResolutionUnit', 'meter', 'XResolution', res, 'YResolution', res); + + % Isolate the subplot, if it is one + cls = all(ismember(get(fig, 'Type'), {'axes', 'uipanel'})); + if cls + % Given handles of one or more axes, so isolate them from the rest + fig = isolate_axes(fig); + else + % Check we have a figure + if ~isequal(get(fig, 'Type'), 'figure') + error('Handle must be that of a figure, axes or uipanel'); + end + % Get the old InvertHardcopy mode + old_mode = get(fig, 'InvertHardcopy'); end - if options.bmp - imwrite(A, [options.name '.bmp']); + + % Hack the font units where necessary (due to a font rendering bug in print?). + % This may not work perfectly in all cases. + % Also it can change the figure layout if reverted, so use a copy. + magnify = options.magnify * options.aa_factor; + if isbitmap(options) && magnify ~= 1 + fontu = findall(fig, 'FontUnits', 'normalized'); + if ~isempty(fontu) + % Some normalized font units found + if ~cls + fig = copyfig(fig); + set(fig, 'Visible', 'off'); + fontu = findall(fig, 'FontUnits', 'normalized'); + cls = true; + end + set(fontu, 'FontUnits', 'points'); + end end - % Save jpeg with given quality - if options.jpg - quality = options.quality; - if isempty(quality) - quality = 95; + + try + % MATLAB "feature": axes limits and tick marks can change when printing + Hlims = findall(fig, 'Type', 'axes'); + if ~cls + % Record the old axes limit and tick modes + Xlims = make_cell(get(Hlims, 'XLimMode')); + Ylims = make_cell(get(Hlims, 'YLimMode')); + Zlims = make_cell(get(Hlims, 'ZLimMode')); + Xtick = make_cell(get(Hlims, 'XTickMode')); + Ytick = make_cell(get(Hlims, 'YTickMode')); + Ztick = make_cell(get(Hlims, 'ZTickMode')); + Xlabel = make_cell(get(Hlims, 'XTickLabelMode')); + Ylabel = make_cell(get(Hlims, 'YTickLabelMode')); + Zlabel = make_cell(get(Hlims, 'ZTickLabelMode')); end - if quality > 100 - imwrite(A, [options.name '.jpg'], 'Mode', 'lossless'); - else - imwrite(A, [options.name '.jpg'], 'Quality', quality); + + % Set all axes limit and tick modes to manual, so the limits and ticks can't change + % Fix Matlab R2014b bug (issue #34): plot markers are not displayed when ZLimMode='manual' + set(Hlims, 'XLimMode', 'manual', 'YLimMode', 'manual'); + set_tick_mode(Hlims, 'X'); + set_tick_mode(Hlims, 'Y'); + if ~using_hg2(fig) + set(Hlims,'ZLimMode', 'manual'); + set_tick_mode(Hlims, 'Z'); end + catch + % ignore - fix issue #4 (using HG2 on R2014a and earlier) end - % Save tif images in cmyk if wanted (and possible) - if options.tif - if options.colourspace == 1 && size(A, 3) == 3 - A = double(255 - A); - K = min(A, [], 3); - K_ = 255 ./ max(255 - K, 1); - C = (A(:,:,1) - K) .* K_; - M = (A(:,:,2) - K) .* K_; - Y = (A(:,:,3) - K) .* K_; - A = uint8(cat(3, C, M, Y, K)); - clear C M Y K K_ + + % Fix issue #21 (bold TeX axes labels/titles in R2014b when exporting to EPS/PDF) + try + if using_hg2(fig) && isvector(options) + % Set the FontWeight of axes labels/titles to 'normal' + % Fix issue #69: set non-bold font only if the string contains symbols (\beta etc.) + % Issue #243: only set non-bold font (workaround for issue #69) in R2015b or earlier + try isPreR2016a = verLessThan('matlab','8.7'); catch, isPreR2016a = true; end + if isPreR2016a + texLabels = findall(fig, 'type','text', 'FontWeight','bold'); + symbolIdx = ~cellfun('isempty',strfind({texLabels.String},'\')); + if ~isempty(symbolIdx) + set(texLabels(symbolIdx), 'FontWeight','normal'); + warning('export_fig:BoldTexLabels', 'Bold labels with Tex symbols converted into non-bold in export_fig (fix for issue #69)'); + end + end end - append_mode = {'overwrite', 'append'}; - imwrite(A, [options.name '.tif'], 'Resolution', options.magnify*get(0, 'ScreenPixelsPerInch'), 'WriteMode', append_mode{options.append+1}); - end -end -% Now do the vector formats -if isvector(options) - % Set the default renderer to painters - if ~options.renderer - renderer = '-painters'; - end - % Generate some filenames - tmp_nam = [tempname '.eps']; - if options.pdf - pdf_nam = [options.name '.pdf']; - else - pdf_nam = [tempname '.pdf']; - end - % Generate the options for print - p2eArgs = {renderer, sprintf('-r%d', options.resolution)}; - if options.colourspace == 1 - p2eArgs = [p2eArgs {'-cmyk'}]; - end - if ~options.crop - p2eArgs = [p2eArgs {'-loose'}]; + catch + % ignore end + + % Fix issue #42: non-normalized annotations on HG1 (internal Matlab bug) + annotationHandles = []; try - % Generate an eps - print2eps(tmp_nam, fig, p2eArgs{:}); - % Remove the background, if desired - if options.transparent && ~isequal(get(fig, 'Color'), 'none') - eps_remove_background(tmp_nam); - end - % Add a bookmark to the PDF if desired - if options.bookmark - fig_nam = get(fig, 'Name'); - if isempty(fig_nam) - warning('export_fig:EmptyBookmark', 'Bookmark requested for figure with no name. Bookmark will be empty.'); + if ~using_hg2(fig) + annotationHandles = findall(fig,'Type','hggroup','-and','-property','Units','-and','-not','Units','norm'); + try % suggested by Jesús Pestana Puerta (jespestana) 30/9/2015 + originalUnits = get(annotationHandles,'Units'); + set(annotationHandles,'Units','norm'); + catch end - add_bookmark(tmp_nam, fig_nam); end - % Generate a pdf - eps2pdf(tmp_nam, pdf_nam, 1, options.append, options.colourspace==2, options.quality); - catch ex - % Delete the eps - delete(tmp_nam); - rethrow(ex); + catch + % should never happen, but ignore in any case - issue #50 end - % Delete the eps - delete(tmp_nam); - if options.eps - try - % Generate an eps from the pdf - pdf2eps(pdf_nam, [options.name '.eps']); - catch ex - if ~options.pdf - % Delete the pdf - delete(pdf_nam); - end - rethrow(ex); + + % Fix issue #46: Ghostscript crash if figure units <> pixels + oldFigUnits = get(fig,'Units'); + set(fig,'Units','pixels'); + + % Set to print exactly what is there + if options.invert_hardcopy + try set(fig, 'InvertHardcopy', 'off'); catch, end % fail silently in uifigures + end + + % Set the renderer + switch options.renderer + case 1 + renderer = '-opengl'; + case 2 + renderer = '-zbuffer'; + case 3 + renderer = '-painters'; + otherwise + renderer = '-opengl'; % Default for bitmaps + end + + hImages = findall(fig,'type','image'); + + % Handle transparent patches + hasTransparency = ~isempty(findall(fig,'-property','FaceAlpha','-and','-not','FaceAlpha',1)); + hasPatches = ~isempty(findall(fig,'type','patch')); + if hasTransparency + % Alert if trying to export transparent patches/areas to non-supported outputs (issue #108) + % http://www.mathworks.com/matlabcentral/answers/265265-can-export_fig-or-else-draw-vector-graphics-with-transparent-surfaces + % TODO - use transparency when exporting to PDF by not passing via print2eps + msg = 'export_fig currently supports transparent patches/areas only in PNG output. '; + if options.pdf + warning('export_fig:transparency', '%s\nTo export transparent patches/areas to PDF, use the print command:\n print(gcf, ''-dpdf'', ''%s.pdf'');', msg, options.name); + elseif ~options.png && ~options.tif % issue #168 + warning('export_fig:transparency', '%s\nTo export the transparency correctly, try using the ScreenCapture utility on the Matlab File Exchange: http://bit.ly/1QFrBip', msg); end - if ~options.pdf - % Delete the pdf - delete(pdf_nam); + elseif ~isempty(hImages) + % Fix for issue #230: use OpenGL renderer when exported image contains transparency + for idx = 1 : numel(hImages) + cdata = get(hImages(idx),'CData'); + if any(isnan(cdata(:))) + hasTransparency = true; + break + end end end -end -if cls - % Close the created figure - close(fig); -else - % Reset the hardcopy mode - set(fig, 'InvertHardcopy', old_mode); - % Reset the axes limit and tick modes - for a = 1:numel(Hlims) - set(Hlims(a), 'XLimMode', Xlims{a}, 'YLimMode', Ylims{a}, 'ZLimMode', Zlims{a}, 'XTickMode', Xtick{a}, 'YTickMode', Ytick{a}, 'ZTickMode', Ztick{a}); - end -end -return - -function [fig, options] = parse_args(nout, varargin) -% Parse the input arguments -% Set the defaults -fig = get(0, 'CurrentFigure'); -options = struct('name', 'export_fig_out', ... - 'crop', true, ... - 'transparent', false, ... - 'renderer', 0, ... % 0: default, 1: OpenGL, 2: ZBuffer, 3: Painters - 'pdf', false, ... - 'eps', false, ... - 'png', false, ... - 'tif', false, ... - 'jpg', false, ... - 'bmp', false, ... - 'colourspace', 0, ... % 0: RGB/gray, 1: CMYK, 2: gray - 'append', false, ... - 'im', nout == 1, ... - 'alpha', nout == 2, ... - 'aa_factor', 3, ... - 'magnify', [], ... - 'resolution', [], ... - 'bookmark', false, ... - 'quality', []); -native = false; % Set resolution to native of an image - -% Go through the other arguments -for a = 1:nargin-1 - if all(ishandle(varargin{a})) - fig = varargin{a}; - elseif ischar(varargin{a}) && ~isempty(varargin{a}) - if varargin{a}(1) == '-' - switch lower(varargin{a}(2:end)) - case 'nocrop' - options.crop = false; - case {'trans', 'transparent'} - options.transparent = true; - case 'opengl' - options.renderer = 1; - case 'zbuffer' - options.renderer = 2; - case 'painters' - options.renderer = 3; - case 'pdf' - options.pdf = true; - case 'eps' - options.eps = true; - case 'png' - options.png = true; - case {'tif', 'tiff'} - options.tif = true; - case {'jpg', 'jpeg'} - options.jpg = true; - case 'bmp' - options.bmp = true; - case 'rgb' - options.colourspace = 0; - case 'cmyk' - options.colourspace = 1; - case {'gray', 'grey'} - options.colourspace = 2; - case {'a1', 'a2', 'a3', 'a4'} - options.aa_factor = str2double(varargin{a}(3)); - case 'append' - options.append = true; - case 'bookmark' - options.bookmark = true; - case 'native' - native = true; - otherwise - val = str2double(regexp(varargin{a}, '(?<=-(m|M|r|R|q|Q))(\d*\.)?\d+(e-?\d+)?', 'match')); - if ~isscalar(val) - error('option %s not recognised', varargin{a}); + + try + % Do the bitmap formats first + if isbitmap(options) + if abs(options.bb_padding) > 1 + displaySuggestedWorkarounds = false; + error('For bitmap output (png,jpg,tif,bmp) the padding value (-p) must be between -1 100 + imwrite(A, [options.name '.jpg'], 'Mode', 'lossless'); + else + imwrite(A, [options.name '.jpg'], 'Quality', quality); + end + end + % Save tif images in cmyk if wanted (and possible) + if options.tif + if options.colourspace == 1 && size(A, 3) == 3 + A = double(255 - A); + K = min(A, [], 3); + K_ = 255 ./ max(255 - K, 1); + C = (A(:,:,1) - K) .* K_; + M = (A(:,:,2) - K) .* K_; + Y = (A(:,:,3) - K) .* K_; + A = uint8(cat(3, C, M, Y, K)); + clear C M Y K K_ + end + append_mode = {'overwrite', 'append'}; + imwrite(A, [options.name '.tif'], 'Resolution', options.magnify*get(0, 'ScreenPixelsPerInch'), 'WriteMode', append_mode{options.append+1}); end end - end -end -% Compute the magnification and resolution -if isempty(options.magnify) - if isempty(options.resolution) - options.magnify = 1; - options.resolution = 864; - else - options.magnify = options.resolution ./ get(0, 'ScreenPixelsPerInch'); - end -elseif isempty(options.resolution) - options.resolution = 864; -end - + % Now do the vector formats + if isvector(options) + % Set the default renderer to painters + if ~options.renderer + if hasTransparency || hasPatches + % This is *MUCH* slower, but more accurate for patches and transparent annotations (issue #39) + renderer = '-opengl'; + else + renderer = '-painters'; + end + end + options.rendererStr = renderer; % fix for issue #112 + % Generate some filenames + tmp_nam = [tempname '.eps']; + try + % Ensure that the temp dir is writable (Javier Paredes 30/1/15) + fid = fopen(tmp_nam,'w'); + fwrite(fid,1); + fclose(fid); + delete(tmp_nam); + isTempDirOk = true; + catch + % Temp dir is not writable, so use the user-specified folder + [dummy,fname,fext] = fileparts(tmp_nam); %#ok + fpath = fileparts(options.name); + tmp_nam = fullfile(fpath,[fname fext]); + isTempDirOk = false; + end + if isTempDirOk + pdf_nam_tmp = [tempname '.pdf']; + else + pdf_nam_tmp = fullfile(fpath,[fname '.pdf']); + end + if options.pdf + pdf_nam = [options.name '.pdf']; + try copyfile(pdf_nam, pdf_nam_tmp, 'f'); catch, end % fix for issue #65 + else + pdf_nam = pdf_nam_tmp; + end + % Generate the options for print + p2eArgs = {renderer}; + if ~isempty(options.resolution) % issue #241 + p2eArgs{end+1} = sprintf('-r%d', options.resolution); + end + if options.colourspace == 1 % CMYK + % Issue #33: due to internal bugs in Matlab's print() function, we can't use its -cmyk option + %p2eArgs{end+1} = '-cmyk'; + end + if ~options.crop + % Issue #56: due to internal bugs in Matlab's print() function, we can't use its internal cropping mechanism, + % therefore we always use '-loose' (in print2eps.m) and do our own cropping (in crop_borders) + %p2eArgs{end+1} = '-loose'; + end + if any(strcmpi(varargin,'-depsc')) + % Issue #45: lines in image subplots are exported in invalid color. + % The workaround is to use the -depsc parameter instead of the default -depsc2 + p2eArgs{end+1} = '-depsc'; + end + try + % Remove background if requested (issue #207) + [hXs, hYs, hZs] = deal([]); + if options.transparent %&& ~isequal(get(fig, 'Color'), 'none') + if options.renderer == 1 % OpenGL + warning('export_fig:openglTransparentBG', '-opengl sometimes fails to produce transparent backgrounds; try -painters instead'); + else + originalBgColor = get(fig, 'Color'); + set(fig,'Color','none'); -% Check we have a figure handle -if isempty(fig) - error('No figure found'); -end + % Correct black axes color to off-black (issue #249) + hAxes = findall(fig, 'Type','axes'); + hXs = fixBlackAxle(hAxes, 'XColor'); + hYs = fixBlackAxle(hAxes, 'YColor'); + hZs = fixBlackAxle(hAxes, 'ZColor'); + end + end + % Generate an eps + print2eps(tmp_nam, fig, options, p2eArgs{:}); + % { + % Remove the background, if desired + if options.transparent %&& ~isequal(get(fig, 'Color'), 'none') + eps_remove_background(tmp_nam, 1 + using_hg2(fig)); -% Set the default format -if ~isvector(options) && ~isbitmap(options) - options.png = true; -end + % Revert the black axes colors + set(hXs, 'XColor', [0,0,0]); + set(hYs, 'YColor', [0,0,0]); + set(hZs, 'ZColor', [0,0,0]); + end + %} + % Restore the figure's previous background color (if modified) + try set(fig,'Color',originalBgColor); drawnow; catch, end + % Fix colorspace to CMYK, if requested (workaround for issue #33) + if options.colourspace == 1 % CMYK + % Issue #33: due to internal bugs in Matlab's print() function, we can't use its -cmyk option + change_rgb_to_cmyk(tmp_nam); + end + % Add a bookmark to the PDF if desired + if options.bookmark + fig_nam = get(fig, 'Name'); + if isempty(fig_nam) + warning('export_fig:EmptyBookmark', 'Bookmark requested for figure with no name. Bookmark will be empty.'); + end + add_bookmark(tmp_nam, fig_nam); + end + % Generate a pdf + eps2pdf(tmp_nam, pdf_nam_tmp, 1, options.append, options.colourspace==2, options.quality, options.gs_options); + % Ghostscript croaks on % chars in the output PDF file, so use tempname and then rename the file + try + % Rename the file (except if it is already the same) + % Abbie K's comment on the commit for issue #179 (#commitcomment-20173476) + if ~isequal(pdf_nam_tmp, pdf_nam) + movefile(pdf_nam_tmp, pdf_nam, 'f'); + end + catch + % Alert in case of error creating output PDF/EPS file (issue #179) + if exist(pdf_nam_tmp, 'file') + errMsg = ['Could not create ' pdf_nam ' - perhaps the folder does not exist, or you do not have write permissions, or the file is open in another application']; + error(errMsg); + else + error('Could not generate the intermediary EPS file.'); + end + end + catch ex + % Delete the eps + delete(tmp_nam); + rethrow(ex); + end + % Delete the eps + delete(tmp_nam); + if options.eps || options.linecaps + try + % Generate an eps from the pdf + % since pdftops can't handle relative paths (e.g., '..\'), use a temp file + eps_nam_tmp = strrep(pdf_nam_tmp,'.pdf','.eps'); + pdf2eps(pdf_nam, eps_nam_tmp); -% Check whether transparent background is wanted (old way) -if isequal(get(ancestor(fig(1), 'figure'), 'Color'), 'none') - options.transparent = true; -end + % Issue #192: enable rounded line-caps + if options.linecaps + fstrm = read_write_entire_textfile(eps_nam_tmp); + fstrm = regexprep(fstrm, '[02] J', '1 J'); + read_write_entire_textfile(eps_nam_tmp, fstrm); + if options.pdf + eps2pdf(eps_nam_tmp, pdf_nam, 1, options.append, options.colourspace==2, options.quality, options.gs_options); + end + end -% If requested, set the resolution to the native vertical resolution of the -% first suitable image found -if native && isbitmap(options) - % Find a suitable image - list = findobj(fig, 'Type', 'image', 'Tag', 'export_fig_native'); - if isempty(list) - list = findobj(fig, 'Type', 'image', 'Visible', 'on'); - end - for hIm = list(:)' - % Check height is >= 2 - height = size(get(hIm, 'CData'), 1); - if height < 2 - continue + if options.eps + movefile(eps_nam_tmp, [options.name '.eps'], 'f'); + else % if options.pdf + try delete(eps_nam_tmp); catch, end + end + catch ex + if ~options.pdf + % Delete the pdf + delete(pdf_nam); + end + try delete(eps_nam_tmp); catch, end + rethrow(ex); + end + if ~options.pdf + % Delete the pdf + delete(pdf_nam); + end + end + % Issue #206: warn if the figure contains an image + if ~isempty(hImages) && strcmpi(renderer,'-opengl') % see addendum to issue #206 + warnMsg = ['exporting images to PDF/EPS may result in blurry images on some viewers. ' ... + 'If so, try to change viewer, or increase the image''s CData resolution, or use -opengl renderer, or export via the print function. ' ... + 'See issue #206 for details.']; + warning('export_fig:pdf_eps:blurry_image', warnMsg); + end end - % Account for the image filling only part of the axes, or vice - % versa - yl = get(hIm, 'YData'); - if isscalar(yl) - yl = [yl(1)-0.5 yl(1)+height+0.5]; + + % Revert the figure or close it (if requested) + if cls || options.closeFig + % Close the created figure + close(fig); else - if ~diff(yl) - continue + % Reset the hardcopy mode + try set(fig, 'InvertHardcopy', old_mode); catch, end % fail silently in uifigures + % Reset the axes limit and tick modes + for a = 1:numel(Hlims) + try + set(Hlims(a), 'XLimMode', Xlims{a}, 'YLimMode', Ylims{a}, 'ZLimMode', Zlims{a},... + 'XTickMode', Xtick{a}, 'YTickMode', Ytick{a}, 'ZTickMode', Ztick{a},... + 'XTickLabelMode', Xlabel{a}, 'YTickLabelMode', Ylabel{a}, 'ZTickLabelMode', Zlabel{a}); + catch + % ignore - fix issue #4 (using HG2 on R2014a and earlier) + end + end + % Revert the tex-labels font weights + try set(texLabels, 'FontWeight','bold'); catch, end + % Revert annotation units + for handleIdx = 1 : numel(annotationHandles) + try + oldUnits = originalUnits{handleIdx}; + catch + oldUnits = originalUnits; + end + try set(annotationHandles(handleIdx),'Units',oldUnits); catch, end + end + % Revert figure units + set(fig,'Units',oldFigUnits); + end + + % Output to clipboard (if requested) + if options.clipboard + % Delete the output file if unchanged from the default name ('export_fig_out.png') + if strcmpi(options.name,'export_fig_out') + try + fileInfo = dir('export_fig_out.png'); + if ~isempty(fileInfo) + timediff = now - fileInfo.datenum; + ONE_SEC = 1/24/60/60; + if timediff < ONE_SEC + delete('export_fig_out.png'); + end + end + catch + % never mind... + end + end + + % Save the image in the system clipboard + % credit: Jiro Doke's IMCLIPBOARD: http://www.mathworks.com/matlabcentral/fileexchange/28708-imclipboard + try + error(javachk('awt', 'export_fig -clipboard output')); + catch + warning('export_fig:clipboardJava', 'export_fig -clipboard output failed: requires Java to work'); + return; + end + try + % Import necessary Java classes + import java.awt.Toolkit + import java.awt.image.BufferedImage + import java.awt.datatransfer.DataFlavor + + % Get System Clipboard object (java.awt.Toolkit) + cb = Toolkit.getDefaultToolkit.getSystemClipboard(); + + % Add java class (ImageSelection) to the path + if ~exist('ImageSelection', 'class') + javaaddpath(fileparts(which(mfilename)), '-end'); + end + + % Get image size + ht = size(imageData, 1); + wd = size(imageData, 2); + + % Convert to Blue-Green-Red format + try + imageData2 = imageData(:, :, [3 2 1]); + catch + % Probably gray-scaled image (2D, without the 3rd [RGB] dimension) + imageData2 = imageData(:, :, [1 1 1]); + end + + % Convert to 3xWxH format + imageData2 = permute(imageData2, [3, 2, 1]); + + % Append Alpha data (unused - transparency is not supported in clipboard copy) + alphaData2 = uint8(permute(255*alpha,[3,2,1])); %=255*ones(1,wd,ht,'uint8') + imageData2 = cat(1, imageData2, alphaData2); + + % Create image buffer + imBuffer = BufferedImage(wd, ht, BufferedImage.TYPE_INT_RGB); + imBuffer.setRGB(0, 0, wd, ht, typecast(imageData2(:), 'int32'), 0, wd); + + % Create ImageSelection object from the image buffer + imSelection = ImageSelection(imBuffer); + + % Set clipboard content to the image + cb.setContents(imSelection, []); + catch + warning('export_fig:clipboardFailed', 'export_fig -clipboard output failed: %s', lasterr); %#ok end - yl = yl + [-0.5 0.5] * (diff(yl) / (height - 1)); end - hAx = get(hIm, 'Parent'); - yl2 = get(hAx, 'YLim'); - % Find the pixel height of the axes - oldUnits = get(hAx, 'Units'); - set(hAx, 'Units', 'pixels'); - pos = get(hAx, 'Position'); - set(hAx, 'Units', oldUnits); - if ~pos(4) - continue + + % Don't output the data to console unless requested + if ~nargout + clear imageData alpha end - % Found a suitable image - % Account for stretch-to-fill being disabled - pbar = get(hAx, 'PlotBoxAspectRatio'); - pos = min(pos(4), pbar(2)*pos(3)/pbar(1)); - % Set the magnification to give native resolution - options.magnify = (height * diff(yl2)) / (pos * diff(yl)); - break + catch err + % Display possible workarounds before the error message + if displaySuggestedWorkarounds && ~strcmpi(err.message,'export_fig error') + if ~hadError, fprintf(2, 'export_fig error. '); end + fprintf(2, 'Please ensure:\n'); + fprintf(2, ' that you are using the latest version of export_fig\n'); + if ismac + fprintf(2, ' and that you have Ghostscript installed\n'); + else + fprintf(2, ' and that you have Ghostscript installed\n'); + end + try + if options.eps + fprintf(2, ' and that you have pdftops installed\n'); + end + catch + % ignore - probably an error in parse_args + end + fprintf(2, ' and that you do not have multiple versions of export_fig installed by mistake\n'); + fprintf(2, ' and that you did not made a mistake in the expected input arguments\n'); + try + % Alert per issue #149 + if ~strncmpi(get(0,'Units'),'pixel',5) + fprintf(2, ' or try to set groot''s Units property back to its default value of ''pixels'' (details)\n'); + end + catch + % ignore - maybe an old MAtlab release + end + fprintf(2, '\nIf the problem persists, then please report a new issue.\n\n'); + end + rethrow(err) end end -return -function A = downsize(A, factor) -% Downsample an image -if factor == 1 - % Nothing to do - return -end -try - % Faster, but requires image processing toolbox - A = imresize(A, 1/factor, 'bilinear'); -catch - % No image processing toolbox - resize manually - % Lowpass filter - use Gaussian as is separable, so faster - % Compute the 1d Gaussian filter - filt = (-factor-1:factor+1) / (factor * 0.6); - filt = exp(-filt .* filt); - % Normalize the filter - filt = single(filt / sum(filt)); - % Filter the image - padding = floor(numel(filt) / 2); - for a = 1:size(A, 3) - A(:,:,a) = conv2(filt, filt', single(A([ones(1, padding) 1:end repmat(end, 1, padding)],[ones(1, padding) 1:end repmat(end, 1, padding)],a)), 'valid'); - end - % Subsample - A = A(1+floor(mod(end-1, factor)/2):factor:end,1+floor(mod(end-1, factor)/2):factor:end,:); +function options = default_options() + % Default options used by export_fig + options = struct(... + 'name', 'export_fig_out', ... + 'crop', true, ... + 'crop_amounts', nan(1,4), ... % auto-crop all 4 image sides + 'transparent', false, ... + 'renderer', 0, ... % 0: default, 1: OpenGL, 2: ZBuffer, 3: Painters + 'pdf', false, ... + 'eps', false, ... + 'png', false, ... + 'tif', false, ... + 'jpg', false, ... + 'bmp', false, ... + 'clipboard', false, ... + 'colourspace', 0, ... % 0: RGB/gray, 1: CMYK, 2: gray + 'append', false, ... + 'im', false, ... + 'alpha', false, ... + 'aa_factor', 0, ... + 'bb_padding', 0, ... + 'magnify', [], ... + 'resolution', [], ... + 'bookmark', false, ... + 'closeFig', false, ... + 'quality', [], ... + 'update', false, ... + 'fontswap', true, ... + 'font_space', '', ... + 'linecaps', false, ... + 'invert_hardcopy', true, ... + 'gs_options', {{}}); end -return -function A = rgb2grey(A) -A = cast(reshape(reshape(single(A), [], 3) * single([0.299; 0.587; 0.114]), size(A, 1), size(A, 2)), class(A)); -return +function [fig, options] = parse_args(nout, fig, varargin) + % Parse the input arguments -function A = check_greyscale(A) -% Check if the image is greyscale -if size(A, 3) == 3 && ... - all(reshape(A(:,:,1) == A(:,:,2), [], 1)) && ... - all(reshape(A(:,:,2) == A(:,:,3), [], 1)) - A = A(:,:,1); % Save only one channel for 8-bit output -end -return + % Convert strings => chars + varargin = cellfun(@str2char,varargin,'un',false); -function [A, v] = crop_background(A, bcol) -% Map the foreground pixels -[h, w, c] = size(A); -if isscalar(bcol) && c > 1 - bcol = bcol(ones(1, c)); -end -bail = false; -for l = 1:w - for a = 1:c - if ~all(A(:,l,a) == bcol(a)) - bail = true; - break; + % Set the defaults + native = false; % Set resolution to native of an image + options = default_options(); + options.im = (nout == 1); % user requested imageData output + options.alpha = (nout == 2); % user requested alpha output + + % Go through the other arguments + skipNext = false; + for a = 1:nargin-2 + if skipNext + skipNext = false; + continue; + end + if all(ishandle(varargin{a})) + fig = varargin{a}; + elseif ischar(varargin{a}) && ~isempty(varargin{a}) + if varargin{a}(1) == '-' + switch lower(varargin{a}(2:end)) + case 'nocrop' + options.crop = false; + options.crop_amounts = [0,0,0,0]; + case {'trans', 'transparent'} + options.transparent = true; + case 'opengl' + options.renderer = 1; + case 'zbuffer' + options.renderer = 2; + case 'painters' + options.renderer = 3; + case 'pdf' + options.pdf = true; + case 'eps' + options.eps = true; + case 'png' + options.png = true; + case {'tif', 'tiff'} + options.tif = true; + case {'jpg', 'jpeg'} + options.jpg = true; + case 'bmp' + options.bmp = true; + case 'rgb' + options.colourspace = 0; + case 'cmyk' + options.colourspace = 1; + case {'gray', 'grey'} + options.colourspace = 2; + case {'a1', 'a2', 'a3', 'a4'} + options.aa_factor = str2double(varargin{a}(3)); + case 'append' + options.append = true; + case 'bookmark' + options.bookmark = true; + case 'native' + native = true; + case 'clipboard' + options.clipboard = true; + options.im = true; + options.alpha = true; + case 'svg' + filename = strrep(options.name,'export_fig_out','filename'); + msg = ['SVG output is not supported by export_fig. Use one of the following alternatives:\n' ... + ' 1. saveas(gcf,''' filename '.svg'')\n' ... + ' 2. plot2svg utility: https://github.com/kupiqu/plot2svg\n' ... % Note: replaced defunct https://github.com/jschwizer99/plot2svg with up-to-date fork on https://github.com/kupiqu/plot2svg + ' 3. export_fig to EPS/PDF, then convert to SVG using generic (non-Matlab) tools\n']; + error(sprintf(msg)); %#ok + case 'update' + % Download the latest version of export_fig into the export_fig folder + try + zipFileName = 'https://github.com/altmany/export_fig/archive/master.zip'; + folderName = fileparts(which(mfilename('fullpath'))); + targetFileName = fullfile(folderName, datestr(now,'yyyy-mm-dd.zip')); + urlwrite(zipFileName,targetFileName); + catch + error('Could not download %s into %s\n',zipFileName,targetFileName); + end + + % Unzip the downloaded zip file in the export_fig folder + try + unzip(targetFileName,folderName); + catch + error('Could not unzip %s\n',targetFileName); + end + case 'nofontswap' + options.fontswap = false; + case 'font_space' + options.font_space = varargin{a+1}; + skipNext = true; + case 'linecaps' + options.linecaps = true; + case 'noinvert' + options.invert_hardcopy = false; + otherwise + try + wasError = false; + if strcmpi(varargin{a}(1:2),'-d') + varargin{a}(2) = 'd'; % ensure lowercase 'd' + options.gs_options{end+1} = varargin{a}; + elseif strcmpi(varargin{a}(1:2),'-c') + if numel(varargin{a})==2 + skipNext = true; + vals = str2num(varargin{a+1}); %#ok + else + vals = str2num(varargin{a}(3:end)); %#ok + end + if numel(vals)~=4 + wasError = true; + error('option -c cannot be parsed: must be a 4-element numeric vector'); + end + options.crop_amounts = vals; + options.crop = true; + else % scalar parameter value + val = str2double(regexp(varargin{a}, '(?<=-(m|M|r|R|q|Q|p|P))-?\d*.?\d+', 'match')); + if isempty(val) || isnan(val) + % Issue #51: improved processing of input args (accept space between param name & value) + val = str2double(varargin{a+1}); + if isscalar(val) && ~isnan(val) + skipNext = true; + end + end + if ~isscalar(val) || isnan(val) + wasError = true; + error('option %s is not recognised or cannot be parsed', varargin{a}); + end + switch lower(varargin{a}(2)) + case 'm' + % Magnification may never be negative + if val <= 0 + wasError = true; + error('Bad magnification value: %g (must be positive)', val); + end + options.magnify = val; + case 'r' + options.resolution = val; + case 'q' + options.quality = max(val, 0); + case 'p' + options.bb_padding = val; + end + end + catch err + % We might have reached here by raising an intentional error + if wasError % intentional raise + rethrow(err) + else % unintentional + error(['Unrecognized export_fig input option: ''' varargin{a} '''']); + end + end + end + else + [p, options.name, ext] = fileparts(varargin{a}); + if ~isempty(p) + % Issue #221: alert if the requested folder does not exist + if ~exist(p,'dir'), error(['Folder ' p ' does not exist!']); end + options.name = [p filesep options.name]; + end + switch lower(ext) + case {'.tif', '.tiff'} + options.tif = true; + case {'.jpg', '.jpeg'} + options.jpg = true; + case '.png' + options.png = true; + case '.bmp' + options.bmp = true; + case '.eps' + options.eps = true; + case '.pdf' + options.pdf = true; + case '.fig' + % If no open figure, then load the specified .fig file and continue + if isempty(fig) + fig = openfig(varargin{a},'invisible'); + varargin{a} = fig; + options.closeFig = true; + else + % save the current figure as the specified .fig file and exit + saveas(fig(1),varargin{a}); + fig = -1; + return + end + case '.svg' + filename = strrep(options.name,'export_fig_out','filename'); + msg = ['SVG output is not supported by export_fig. Use one of the following alternatives:\n' ... + ' 1. saveas(gcf,''' filename '.svg'')\n' ... + ' 2. plot2svg utility: https://github.com/kupiqu/plot2svg\n' ... % Note: replaced defunct https://github.com/jschwizer99/plot2svg with up-to-date fork on https://github.com/kupiqu/plot2svg + ' 3. export_fig to EPS/PDF, then convert to SVG using generic (non-Matlab) tools\n']; + error(sprintf(msg)); %#ok + otherwise + options.name = varargin{a}; + end + end end end - if bail - break; + + % Quick bail-out if no figure found + if isempty(fig), return; end + + % Do border padding with repsect to a cropped image + if options.bb_padding + options.crop = true; end -end -bail = false; -for r = w:-1:l - for a = 1:c - if ~all(A(:,r,a) == bcol(a)) - bail = true; - break; + + % Set default anti-aliasing now we know the renderer + if options.aa_factor == 0 + try isAA = strcmp(get(ancestor(fig, 'figure'), 'GraphicsSmoothing'), 'on'); catch, isAA = false; end + options.aa_factor = 1 + 2 * (~(using_hg2(fig) && isAA) | (options.renderer == 3)); + end + + % Convert user dir '~' to full path + if numel(options.name) > 2 && options.name(1) == '~' && (options.name(2) == '/' || options.name(2) == '\') + options.name = fullfile(char(java.lang.System.getProperty('user.home')), options.name(2:end)); + end + + % Compute the magnification and resolution + if isempty(options.magnify) + if isempty(options.resolution) + options.magnify = 1; + options.resolution = 864; + else + options.magnify = options.resolution ./ get(0, 'ScreenPixelsPerInch'); end + elseif isempty(options.resolution) + options.resolution = 864; end - if bail - break; + + % Set the default format + if ~isvector(options) && ~isbitmap(options) + options.png = true; end -end -bail = false; -for t = 1:h - for a = 1:c - if ~all(A(t,:,a) == bcol(a)) - bail = true; - break; + + % Check whether transparent background is wanted (old way) + if isequal(get(ancestor(fig(1), 'figure'), 'Color'), 'none') + options.transparent = true; + end + + % If requested, set the resolution to the native vertical resolution of the + % first suitable image found + if native + if isbitmap(options) + % Find a suitable image + list = findall(fig, 'Type','image', 'Tag','export_fig_native'); + if isempty(list) + list = findall(fig, 'Type','image', 'Visible','on'); + end + for hIm = list(:)' + % Check height is >= 2 + height = size(get(hIm, 'CData'), 1); + if height < 2 + continue + end + % Account for the image filling only part of the axes, or vice versa + yl = get(hIm, 'YData'); + if isscalar(yl) + yl = [yl(1)-0.5 yl(1)+height+0.5]; + else + yl = [min(yl), max(yl)]; % fix issue #151 (case of yl containing more than 2 elements) + if ~diff(yl) + continue + end + yl = yl + [-0.5 0.5] * (diff(yl) / (height - 1)); + end + hAx = get(hIm, 'Parent'); + yl2 = get(hAx, 'YLim'); + % Find the pixel height of the axes + oldUnits = get(hAx, 'Units'); + set(hAx, 'Units', 'pixels'); + pos = get(hAx, 'Position'); + set(hAx, 'Units', oldUnits); + if ~pos(4) + continue + end + % Found a suitable image + % Account for stretch-to-fill being disabled + pbar = get(hAx, 'PlotBoxAspectRatio'); + pos = min(pos(4), pbar(2)*pos(3)/pbar(1)); + % Set the magnification to give native resolution + options.magnify = abs((height * diff(yl2)) / (pos * diff(yl))); % magnification must never be negative: issue #103 + break + end + elseif options.resolution == 864 % don't use -r864 in vector mode if user asked for -native + options.resolution = []; % issue #241 (internal Matlab bug produces black lines with -r864) end end - if bail - break; +end + +% Convert a possible string => char (issue #245) +function value = str2char(value) + if isa(value,'string') + value = char(value); end end -bail = false; -for b = h:-1:t - for a = 1:c - if ~all(A(b,:,a) == bcol(a)) - bail = true; - break; - end + +function A = downsize(A, factor) + % Downsample an image + if factor == 1 + % Nothing to do + return end - if bail - break; + try + % Faster, but requires image processing toolbox + A = imresize(A, 1/factor, 'bilinear'); + catch + % No image processing toolbox - resize manually + % Lowpass filter - use Gaussian as is separable, so faster + % Compute the 1d Gaussian filter + filt = (-factor-1:factor+1) / (factor * 0.6); + filt = exp(-filt .* filt); + % Normalize the filter + filt = single(filt / sum(filt)); + % Filter the image + padding = floor(numel(filt) / 2); + for a = 1:size(A, 3) + A(:,:,a) = conv2(filt, filt', single(A([ones(1, padding) 1:end repmat(end, 1, padding)],[ones(1, padding) 1:end repmat(end, 1, padding)],a)), 'valid'); + end + % Subsample + A = A(1+floor(mod(end-1, factor)/2):factor:end,1+floor(mod(end-1, factor)/2):factor:end,:); end end -% Crop the background, leaving one boundary pixel to avoid bleeding on -% resize -v = [max(t-1, 1) min(b+1, h) max(l-1, 1) min(r+1, w)]; -A = A(v(1):v(2),v(3):v(4),:); -return - -function eps_remove_background(fname) -% Remove the background of an eps file -% Open the file -fh = fopen(fname, 'r+'); -if fh == -1 - error('Not able to open file %s.', fname); + +function A = rgb2grey(A) + A = cast(reshape(reshape(single(A), [], 3) * single([0.299; 0.587; 0.114]), size(A, 1), size(A, 2)), class(A)); % #ok +end + +function A = check_greyscale(A) + % Check if the image is greyscale + if size(A, 3) == 3 && ... + all(reshape(A(:,:,1) == A(:,:,2), [], 1)) && ... + all(reshape(A(:,:,2) == A(:,:,3), [], 1)) + A = A(:,:,1); % Save only one channel for 8-bit output + end end -% Read the file line by line -while true - % Get the next line - l = fgets(fh); - if isequal(l, -1) - break; % Quit, no rectangle found - end - % Check if the line contains the background rectangle - if isequal(regexp(l, ' *0 +0 +\d+ +\d+ +rf *[\n\r]+', 'start'), 1) - % Set the line to whitespace and quit - l(1:regexp(l, '[\n\r]', 'start', 'once')-1) = ' '; - fseek(fh, -numel(l), 0); - fprintf(fh, l); - break; + +function eps_remove_background(fname, count) + % Remove the background of an eps file + % Open the file + fh = fopen(fname, 'r+'); + if fh == -1 + error('Not able to open file %s.', fname); + end + % Read the file line by line + while count + % Get the next line + l = fgets(fh); + if isequal(l, -1) + break; % Quit, no rectangle found + end + % Check if the line contains the background rectangle + if isequal(regexp(l, ' *0 +0 +\d+ +\d+ +r[fe] *[\n\r]+', 'start'), 1) + % Set the line to whitespace and quit + l(1:regexp(l, '[\n\r]', 'start', 'once')-1) = ' '; + fseek(fh, -numel(l), 0); + fprintf(fh, l); + % Reduce the count + count = count - 1; + end end + % Close the file + fclose(fh); end -% Close the file -fclose(fh); -return function b = isvector(options) -b = options.pdf || options.eps; -return + b = options.pdf || options.eps; +end function b = isbitmap(options) -b = options.png || options.tif || options.jpg || options.bmp || options.im || options.alpha; -return + b = options.png || options.tif || options.jpg || options.bmp || options.im || options.alpha; +end % Helper function function A = make_cell(A) -if ~iscell(A) - A = {A}; + if ~iscell(A) + A = {A}; + end end -return function add_bookmark(fname, bookmark_text) -% Adds a bookmark to the temporary EPS file after %%EndPageSetup -% Read in the file -fh = fopen(fname, 'r'); -if fh == -1 - error('File %s not found.', fname); -end -try - fstrm = fread(fh, '*char')'; -catch ex + % Adds a bookmark to the temporary EPS file after %%EndPageSetup + % Read in the file + fh = fopen(fname, 'r'); + if fh == -1 + error('File %s not found.', fname); + end + try + fstrm = fread(fh, '*char')'; + catch ex + fclose(fh); + rethrow(ex); + end + fclose(fh); + + % Include standard pdfmark prolog to maximize compatibility + fstrm = strrep(fstrm, '%%BeginProlog', sprintf('%%%%BeginProlog\n/pdfmark where {pop} {userdict /pdfmark /cleartomark load put} ifelse')); + % Add page bookmark + fstrm = strrep(fstrm, '%%EndPageSetup', sprintf('%%%%EndPageSetup\n[ /Title (%s) /OUT pdfmark',bookmark_text)); + + % Write out the updated file + fh = fopen(fname, 'w'); + if fh == -1 + error('Unable to open %s for writing.', fname); + end + try + fwrite(fh, fstrm, 'char*1'); + catch ex + fclose(fh); + rethrow(ex); + end fclose(fh); - rethrow(ex); end -fclose(fh); -% Include standard pdfmark prolog to maximize compatibility -fstrm = strrep(fstrm, '%%BeginProlog', sprintf('%%%%BeginProlog\n/pdfmark where {pop} {userdict /pdfmark /cleartomark load put} ifelse')); -% Add page bookmark -fstrm = strrep(fstrm, '%%EndPageSetup', sprintf('%%%%EndPageSetup\n[ /Title (%s) /OUT pdfmark',bookmark_text)); +function set_tick_mode(Hlims, ax) + % Set the tick mode of linear axes to manual + % Leave log axes alone as these are tricky + M = get(Hlims, [ax 'Scale']); + if ~iscell(M) + M = {M}; + end + %idx = cellfun(@(c) strcmp(c, 'linear'), M); + idx = find(strcmp(M,'linear')); + %set(Hlims(idx), [ax 'TickMode'], 'manual'); % issue #187 + %set(Hlims(idx), [ax 'TickLabelMode'], 'manual'); % this hides exponent label in HG2! + for idx2 = 1 : numel(idx) + try + % Fix for issue #187 - only set manual ticks when no exponent is present + hAxes = Hlims(idx(idx2)); + props = {[ax 'TickMode'],'manual', [ax 'TickLabelMode'],'manual'}; + tickVals = get(hAxes,[ax 'Tick']); + tickStrs = get(hAxes,[ax 'TickLabel']); + try % Fix issue #236 + exponents = [hAxes.([ax 'Axis']).SecondaryLabel]; + catch + exponents = [hAxes.([ax 'Ruler']).SecondaryLabel]; + end + if isempty([exponents.String]) + % Fix for issue #205 - only set manual ticks when the Ticks number match the TickLabels number + if numel(tickVals) == numel(tickStrs) + set(hAxes, props{:}); % no exponent and matching ticks, so update both ticks and tick labels to manual + end + end + catch % probably HG1 + % Fix for issue #220 - exponent is removed in HG1 when TickMode is 'manual' (internal Matlab bug) + if isequal(tickVals, str2num(tickStrs)') %#ok + set(hAxes, props{:}); % revert back to old behavior + end + end + end +end + +function change_rgb_to_cmyk(fname) % convert RGB => CMYK within an EPS file + % Do post-processing on the eps file + try + % Read the EPS file into memory + fstrm = read_write_entire_textfile(fname); -% Write out the updated file -fh = fopen(fname, 'w'); -if fh == -1 - error('Unable to open %s for writing.', fname); + % Replace all gray-scale colors + fstrm = regexprep(fstrm, '\n([\d.]+) +GC\n', '\n0 0 0 ${num2str(1-str2num($1))} CC\n'); + + % Replace all RGB colors + fstrm = regexprep(fstrm, '\n[0.]+ +[0.]+ +[0.]+ +RC\n', '\n0 0 0 1 CC\n'); % pure black + fstrm = regexprep(fstrm, '\n([\d.]+) +([\d.]+) +([\d.]+) +RC\n', '\n${sprintf(''%.4g '',[1-[str2num($1),str2num($2),str2num($3)]/max([str2num($1),str2num($2),str2num($3)]),1-max([str2num($1),str2num($2),str2num($3)])])} CC\n'); + + % Overwrite the file with the modified contents + read_write_entire_textfile(fname, fstrm); + catch + % never mind - leave as is... + end end -try - fwrite(fh, fstrm, 'char*1'); -catch ex - fclose(fh); - rethrow(ex); + +function hBlackAxles = fixBlackAxle(hAxes, axleName) + hBlackAxles = []; + for idx = 1 : numel(hAxes) + ax = hAxes(idx); + axleColor = get(ax, axleName); + if isequal(axleColor,[0,0,0]) || isequal(axleColor,'k') + hBlackAxles(end+1) = ax; %#ok + end + end + set(hBlackAxles, axleName, [0,0,0.01]); % off-black end -fclose(fh); -return \ No newline at end of file diff --git a/tools/plotting/export_fig/fix_lines.m b/tools/plotting/export_fig/fix_lines.m old mode 100755 new mode 100644 index bd51862..b160fd8 --- a/tools/plotting/export_fig/fix_lines.m +++ b/tools/plotting/export_fig/fix_lines.m @@ -1,9 +1,9 @@ -function fix_lines(fname, fname2) %FIX_LINES Improves the line style of eps files generated by print % % Examples: % fix_lines fname % fix_lines fname fname2 +% fstrm_out = fixlines(fstrm_in) % % This function improves the style of lines in eps files generated by % MATLAB's print function, making them more similar to those seen on @@ -15,11 +15,15 @@ function fix_lines(fname, fname2) % order to allow programs such as Ghostscript to find the bounding box % information. % -% IN: +%IN: % fname - Name or path of source eps file. % fname2 - Name or path of destination eps file. Default: same as fname. +% fstrm_in - File contents of a MATLAB-generated eps file. +% +%OUT: +% fstrm_out - Contents of the eps file with line styles fixed. -% Copyright: (C) Oliver Woodford, 2008-2010 +% Copyright: (C) Oliver Woodford, 2008-2014 % The idea of editing the EPS file to change line styles comes from Jiro % Doke's FIXPSLINESTYLE (fex id: 17928) @@ -33,24 +37,30 @@ function fix_lines(fname, fname2) % Thank you to Laurence K for suggesting the check to see if the file was % opened. -% Read in the file -fh = fopen(fname, 'r'); -if fh == -1 - error('File %s not found.', fname); +% 01/03/15: Issue #20: warn users if using this function in HG2 (R2014b+) +% 27/03/15: Fixed out of memory issue with enormous EPS files (generated by print() with OpenGL renderer), related to issue #39 + +function fstrm = fix_lines(fstrm, fname2) + +% Issue #20: warn users if using this function in HG2 (R2014b+) +if using_hg2 + warning('export_fig:hg2','The fix_lines function should not be used in this Matlab version.'); end -try - fstrm = fread(fh, '*char')'; -catch ex - fclose(fh); - rethrow(ex); + +if nargout == 0 || nargin > 1 + if nargin < 2 + % Overwrite the input file + fname2 = fstrm; + end + % Read in the file + fstrm = read_write_entire_textfile(fstrm); end -fclose(fh); % Move any embedded fonts after the postscript header if strcmp(fstrm(1:15), '%!PS-AdobeFont-') % Find the start and end of the header ind = regexp(fstrm, '[\n\r]%!PS-Adobe-'); - [ind2 ind2] = regexp(fstrm, '[\n\r]%%EndComments[\n\r]+'); + [ind2, ind2] = regexp(fstrm, '[\n\r]%%EndComments[\n\r]+'); % Put the header first if ~isempty(ind) && ~isempty(ind2) && ind(1) < ind2(1) fstrm = fstrm([ind(1)+1:ind2(1) 1:ind(1) ind2(1)+1:end]); @@ -66,7 +76,7 @@ function fix_lines(fname, fname2) regexp(fstrm, '[\n\r]DD[\n\r]')]; ind = sort(ind); % Find line width commands -[ind2 ind3] = regexp(fstrm, '[\n\r]\d* w[\n\r]'); +[ind2, ind3] = regexp(fstrm, '[\n\r]\d* w[\n\r]'); % Go through each line style section and swap with any line width commands % near by b = 1; @@ -111,11 +121,6 @@ function fix_lines(fname, fname2) end end -% Isolate line style definition section -first_sec = strfind(fstrm, '% line types:'); -[second_sec remaining] = strtok(fstrm(first_sec+1:end), '/'); -[remaining remaining] = strtok(remaining, '%'); - % Define the new styles, including the new GR format % Dot and dash lengths have two parts: a constant amount plus a line width % variable amount. The constant amount comes after dpi2point, and the @@ -131,24 +136,16 @@ function fix_lines(fname, fname2) '/DD { [1 dom 1.2 dom 4 dam 1.2 dom] 0 setdash 0 setlinecap } bdef',... % Dot dash lines '/GR { [0 dpi2point mul 4 dpi2point mul] 0 setdash 1 setlinecap } bdef'}; % Grid lines - dot spacing remains constant -if nargin < 2 - % Overwrite the input file - fname2 = fname; -end +% Construct the output +% This is the original (memory-intensive) code: +%first_sec = strfind(fstrm, '% line types:'); % Isolate line style definition section +%[second_sec, remaining] = strtok(fstrm(first_sec+1:end), '/'); +%[remaining, remaining] = strtok(remaining, '%'); +%fstrm = [fstrm(1:first_sec) second_sec sprintf('%s\r', new_style{:}) remaining]; +fstrm = regexprep(fstrm,'(% line types:.+?)/.+?%',['$1',sprintf('%s\r',new_style{:}),'%']); -% Save the file with the section replaced -fh = fopen(fname2, 'w'); -if fh == -1 - error('Unable to open %s for writing.', fname2); +% Write the output file +if nargout == 0 || nargin > 1 + read_write_entire_textfile(fname2, fstrm); end -try - fwrite(fh, fstrm(1:first_sec), 'char*1'); - fwrite(fh, second_sec, 'char*1'); - fprintf(fh, '%s\r', new_style{:}); - fwrite(fh, remaining, 'char*1'); -catch ex - fclose(fh); - rethrow(ex); end -fclose(fh); -return \ No newline at end of file diff --git a/tools/plotting/export_fig/ghostscript.m b/tools/plotting/export_fig/ghostscript.m old mode 100755 new mode 100644 index 37617db..1760877 --- a/tools/plotting/export_fig/ghostscript.m +++ b/tools/plotting/export_fig/ghostscript.m @@ -1,3 +1,4 @@ +function varargout = ghostscript(cmd) %GHOSTSCRIPT Calls a local GhostScript executable with the input command % % Example: @@ -19,128 +20,177 @@ % status - 0 iff command ran without problem. % result - Output from ghostscript. -% Copyright: Oliver Woodford, 2009-2013 - -% Thanks to Jonas Dorn for the fix for the title of the uigetdir window on -% Mac OS. -% Thanks to Nathan Childress for the fix to the default location on 64-bit -% Windows systems. -% 27/4/11 - Find 64-bit Ghostscript on Windows. Thanks to Paul Durack and -% Shaun Kline for pointing out the issue -% 4/5/11 - Thanks to David Chorlian for pointing out an alternative -% location for gs on linux. +% Copyright: Oliver Woodford, 2009-2015, Yair Altman 2015- +%{ +% Thanks to Jonas Dorn for the fix for the title of the uigetdir window on Mac OS. +% Thanks to Nathan Childress for the fix to default location on 64-bit Windows systems. +% 27/04/11 - Find 64-bit Ghostscript on Windows. Thanks to Paul Durack and +% Shaun Kline for pointing out the issue +% 04/05/11 - Thanks to David Chorlian for pointing out an alternative +% location for gs on linux. % 12/12/12 - Add extra executable name on Windows. Thanks to Ratish -% Punnoose for highlighting the issue. -% 28/6/13 - Fix error using GS 9.07 in Linux. Many thanks to Jannick -% Steinbring for proposing the fix. +% Punnoose for highlighting the issue. +% 28/06/13 - Fix error using GS 9.07 in Linux. Many thanks to Jannick +% Steinbring for proposing the fix. +% 24/10/13 - Fix error using GS 9.07 in Linux. Many thanks to Johannes +% for the fix. +% 23/01/14 - Add full path to ghostscript.txt in warning. Thanks to Koen +% Vermeer for raising the issue. +% 27/02/15 - If Ghostscript croaks, display suggested workarounds +% 30/03/15 - Improved performance by caching status of GS path check, if ok +% 14/05/15 - Clarified warning message in case GS path could not be saved +% 29/05/15 - Avoid cryptic error in case the ghostscipt path cannot be saved (issue #74) +% 10/11/15 - Custom GS installation webpage for MacOS. Thanks to Andy Hueni via FEX +%} -function varargout = ghostscript(cmd) -% Initialize any required system calls before calling ghostscript -shell_cmd = ''; -if isunix - shell_cmd = 'export LD_LIBRARY_PATH=""; '; % Avoids an error on Linux with GS 9.07 + try + % Call ghostscript + [varargout{1:nargout}] = system([gs_command(gs_path()) cmd]); + catch err + % Display possible workarounds for Ghostscript croaks + url1 = 'https://github.com/altmany/export_fig/issues/12#issuecomment-61467998'; % issue #12 + url2 = 'https://github.com/altmany/export_fig/issues/20#issuecomment-63826270'; % issue #20 + hg2_str = ''; if using_hg2, hg2_str = ' or Matlab R2014a'; end + fprintf(2, 'Ghostscript error. Rolling back to GS 9.10%s may possibly solve this:\n * %s ',hg2_str,url1,url1); + if using_hg2 + fprintf(2, '(GS 9.10)\n * %s (R2014a)',url2,url2); + end + fprintf('\n\n'); + if ismac || isunix + url3 = 'https://github.com/altmany/export_fig/issues/27'; % issue #27 + fprintf(2, 'Alternatively, this may possibly be due to a font path issue:\n * %s\n\n',url3,url3); + % issue #20 + fpath = which(mfilename); + if isempty(fpath), fpath = [mfilename('fullpath') '.m']; end + fprintf(2, 'Alternatively, if you are using csh, modify shell_cmd from "export..." to "setenv ..."\nat the bottom of %s\n\n',fpath,fpath); + end + rethrow(err); + end end -% Call ghostscript -[varargout{1:nargout}] = system(sprintf('%s"%s" %s', shell_cmd, gs_path, cmd)); -return function path_ = gs_path -% Return a valid path -% Start with the currently set path -path_ = user_string('ghostscript'); -% Check the path works -if check_gs_path(path_) - return -end -% Check whether the binary is on the path -if ispc - bin = {'gswin32c.exe', 'gswin64c.exe', 'gs'}; -else - bin = {'gs'}; -end -for a = 1:numel(bin) - path_ = bin{a}; - if check_store_gs_path(path_) + % Return a valid path + % Start with the currently set path + path_ = user_string('ghostscript'); + % Check the path works + if check_gs_path(path_) return end -end -% Search the obvious places -if ispc - default_location = 'C:\Program Files\gs\'; - dir_list = dir(default_location); - if isempty(dir_list) - default_location = 'C:\Program Files (x86)\gs\'; % Possible location on 64-bit systems - dir_list = dir(default_location); + % Check whether the binary is on the path + if ispc + bin = {'gswin32c.exe', 'gswin64c.exe', 'gs'}; + else + bin = {'gs'}; end - executable = {'\bin\gswin32c.exe', '\bin\gswin64c.exe'}; - ver_num = 0; - % If there are multiple versions, use the newest - for a = 1:numel(dir_list) - ver_num2 = sscanf(dir_list(a).name, 'gs%g'); - if ~isempty(ver_num2) && ver_num2 > ver_num - for b = 1:numel(executable) - path2 = [default_location dir_list(a).name executable{b}]; - if exist(path2, 'file') == 2 - path_ = path2; - ver_num = ver_num2; - end - end - end - end - if check_store_gs_path(path_) - return - end -else - bin = {'/usr/bin/gs', '/usr/local/bin/gs'}; for a = 1:numel(bin) path_ = bin{a}; if check_store_gs_path(path_) return end end -end -% Ask the user to enter the path -while 1 - if strncmp(computer, 'MAC', 3) % Is a Mac - % Give separate warning as the uigetdir dialogue box doesn't have a - % title - uiwait(warndlg('Ghostscript not found. Please locate the program.')) - end - base = uigetdir('/', 'Ghostcript not found. Please locate the program.'); - if isequal(base, 0) - % User hit cancel or closed window - break; + % Search the obvious places + if ispc + default_location = 'C:\Program Files\gs\'; + dir_list = dir(default_location); + if isempty(dir_list) + default_location = 'C:\Program Files (x86)\gs\'; % Possible location on 64-bit systems + dir_list = dir(default_location); + end + executable = {'\bin\gswin32c.exe', '\bin\gswin64c.exe'}; + ver_num = 0; + % If there are multiple versions, use the newest + for a = 1:numel(dir_list) + ver_num2 = sscanf(dir_list(a).name, 'gs%g'); + if ~isempty(ver_num2) && ver_num2 > ver_num + for b = 1:numel(executable) + path2 = [default_location dir_list(a).name executable{b}]; + if exist(path2, 'file') == 2 + path_ = path2; + ver_num = ver_num2; + end + end + end + end + if check_store_gs_path(path_) + return + end + else + executable = {'/usr/bin/gs', '/usr/local/bin/gs'}; + for a = 1:numel(executable) + path_ = executable{a}; + if check_store_gs_path(path_) + return + end + end end - base = [base filesep]; - bin_dir = {'', ['bin' filesep], ['lib' filesep]}; - for a = 1:numel(bin_dir) - for b = 1:numel(bin) - path_ = [base bin_dir{a} bin{b}]; - if exist(path_, 'file') == 2 - if check_store_gs_path(path_) - return + % Ask the user to enter the path + while true + if strncmp(computer, 'MAC', 3) % Is a Mac + % Give separate warning as the uigetdir dialogue box doesn't have a + % title + uiwait(warndlg('Ghostscript not found. Please locate the program.')) + end + base = uigetdir('/', 'Ghostcript not found. Please locate the program.'); + if isequal(base, 0) + % User hit cancel or closed window + break; + end + base = [base filesep]; %#ok + bin_dir = {'', ['bin' filesep], ['lib' filesep]}; + for a = 1:numel(bin_dir) + for b = 1:numel(bin) + path_ = [base bin_dir{a} bin{b}]; + if exist(path_, 'file') == 2 + if check_store_gs_path(path_) + return + end end end end end + if ismac + error('Ghostscript not found. Have you installed it (http://pages.uoregon.edu/koch)?'); + else + error('Ghostscript not found. Have you installed it from www.ghostscript.com?'); + end end -error('Ghostscript not found. Have you installed it from www.ghostscript.com?'); function good = check_store_gs_path(path_) -% Check the path is valid -good = check_gs_path(path_); -if ~good - return -end -% Update the current default path to the path found -if ~user_string('ghostscript', path_) - warning('Path to ghostscript installation could not be saved. Enter it manually in ghostscript.txt.'); - return + % Check the path is valid + good = check_gs_path(path_); + if ~good + return + end + % Update the current default path to the path found + if ~user_string('ghostscript', path_) + filename = fullfile(fileparts(which('user_string.m')), '.ignore', 'ghostscript.txt'); + warning('Path to ghostscript installation could not be saved in %s (perhaps a permissions issue). You can manually create this file and set its contents to %s, to improve performance in future invocations (this warning is safe to ignore).', filename, path_); + return + end end -return function good = check_gs_path(path_) -% Check the path is valid -[good, message] = system(sprintf('"%s" -h', path_)); -good = good == 0; -return \ No newline at end of file + persistent isOk + if isempty(path_) + isOk = false; + elseif ~isequal(isOk,true) + % Check whether the path is valid + [status, message] = system([gs_command(path_) '-h']); %#ok + isOk = status == 0; + end + good = isOk; +end + +function cmd = gs_command(path_) + % Initialize any required system calls before calling ghostscript + % TODO: in Unix/Mac, find a way to determine whether to use "export" (bash) or "setenv" (csh/tcsh) + shell_cmd = ''; + if isunix + shell_cmd = 'export LD_LIBRARY_PATH=""; '; % Avoids an error on Linux with GS 9.07 + end + if ismac + shell_cmd = 'export DYLD_LIBRARY_PATH=""; '; % Avoids an error on Mac with GS 9.07 + end + % Construct the command string + cmd = sprintf('%s"%s" ', shell_cmd, path_); +end diff --git a/tools/plotting/export_fig/im2gif.m b/tools/plotting/export_fig/im2gif.m new file mode 100644 index 0000000..a8d183d --- /dev/null +++ b/tools/plotting/export_fig/im2gif.m @@ -0,0 +1,201 @@ +%IM2GIF Convert a multiframe image to an animated GIF file +% +% Examples: +% im2gif infile +% im2gif infile outfile +% im2gif(A, outfile) +% im2gif(..., '-nocrop') +% im2gif(..., '-nodither') +% im2gif(..., '-ncolors', n) +% im2gif(..., '-loops', n) +% im2gif(..., '-delay', n) +% +% This function converts a multiframe image to an animated GIF. +% +% To create an animation from a series of figures, export to a multiframe +% TIFF file using export_fig, then convert to a GIF, as follows: +% +% for a = 2 .^ (3:6) +% peaks(a); +% export_fig test.tif -nocrop -append +% end +% im2gif('test.tif', '-delay', 0.5); +% +%IN: +% infile - string containing the name of the input image. +% outfile - string containing the name of the output image (must have the +% .gif extension). Default: infile, with .gif extension. +% A - HxWxCxN array of input images, stacked along fourth dimension, to +% be converted to gif. +% -nocrop - option indicating that the borders of the output are not to +% be cropped. +% -nodither - option indicating that dithering is not to be used when +% converting the image. +% -ncolors - option pair, the value of which indicates the maximum number +% of colors the GIF can have. This can also be a quantization +% tolerance, between 0 and 1. Default/maximum: 256. +% -loops - option pair, the value of which gives the number of times the +% animation is to be looped. Default: 65535. +% -delay - option pair, the value of which gives the time, in seconds, +% between frames. Default: 1/15. + +% Copyright (C) Oliver Woodford 2011 + +%{ +% 14/02/18: Merged issue #235: reduced memory usage, improved performance (thanks to @numb7rs) +%} + +function im2gif(A, varargin) + +% Parse the input arguments +[A, options] = parse_args(A, varargin{:}); + +if options.crop ~= 0 + % Crop + A = crop_borders(A, A(ceil(end/2),1,:,1)); +end + +% Convert to indexed image +[h, w, c, n] = size(A); + +% Issue #235: Using unique(A,'rows') on the whole image stack at once causes +% massive memory usage when dealing with large images (at least on Matlab 2017b). +% Running unique(...) on individual frames, then again on the results drastically +% reduces the memory usage & slightly improves the execution time (@numb7rs). +uns = cell(1,size(A,4)); +for nn=1:size(A,4) + uns{nn}=unique(reshape(A(:,:,:,nn), h*w, c),'rows'); +end +map=unique(cell2mat(uns'),'rows'); + +A = reshape(permute(A, [1 2 4 3]), h, w*n, c); + +if size(map, 1) > 256 + dither_str = {'dither', 'nodither'}; + dither_str = dither_str{1+(options.dither==0)}; + if options.ncolors <= 1 + [B, map] = rgb2ind(A, options.ncolors, dither_str); + if size(map, 1) > 256 + [B, map] = rgb2ind(A, 256, dither_str); + end + else + [B, map] = rgb2ind(A, min(round(options.ncolors), 256), dither_str); + end +else + if max(map(:)) > 1 + map = double(map) / 255; + A = double(A) / 255; + end + B = rgb2ind(im2double(A), map); +end +B = reshape(B, h, w, 1, n); + +% Bug fix to rgb2ind +map(B(1)+1,:) = im2double(A(1,1,:)); + +% Save as a gif +imwrite(B, map, options.outfile, 'LoopCount', round(options.loops(1)), 'DelayTime', options.delay); +end + +%% Parse the input arguments +function [A, options] = parse_args(A, varargin) +% Set the defaults +options = struct('outfile', '', ... + 'dither', true, ... + 'crop', true, ... + 'ncolors', 256, ... + 'loops', 65535, ... + 'delay', 1/15); + +% Go through the arguments +a = 0; +n = numel(varargin); +while a < n + a = a + 1; + if ischar(varargin{a}) && ~isempty(varargin{a}) + if varargin{a}(1) == '-' + opt = lower(varargin{a}(2:end)); + switch opt + case 'nocrop' + options.crop = false; + case 'nodither' + options.dither = false; + otherwise + if ~isfield(options, opt) + error('Option %s not recognized', varargin{a}); + end + a = a + 1; + if ischar(varargin{a}) && ~ischar(options.(opt)) + options.(opt) = str2double(varargin{a}); + else + options.(opt) = varargin{a}; + end + end + else + options.outfile = varargin{a}; + end + end +end + +if isempty(options.outfile) + if ~ischar(A) + error('No output filename given.'); + end + % Generate the output filename from the input filename + [path, outfile] = fileparts(A); + options.outfile = fullfile(path, [outfile '.gif']); +end + +if ischar(A) + % Read in the image + A = imread_rgb(A); +end +end + +%% Read image to uint8 rgb array +function [A, alpha] = imread_rgb(name) +% Get file info +info = imfinfo(name); +% Special case formats +switch lower(info(1).Format) + case 'gif' + [A, map] = imread(name, 'frames', 'all'); + if ~isempty(map) + map = uint8(map * 256 - 0.5); % Convert to uint8 for storage + A = reshape(map(uint32(A)+1,:), [size(A) size(map, 2)]); % Assume indexed from 0 + A = permute(A, [1 2 5 4 3]); + end + case {'tif', 'tiff'} + A = cell(numel(info), 1); + for a = 1:numel(A) + [A{a}, map] = imread(name, 'Index', a, 'Info', info); + if ~isempty(map) + map = uint8(map * 256 - 0.5); % Convert to uint8 for storage + A{a} = reshape(map(uint32(A{a})+1,:), [size(A) size(map, 2)]); % Assume indexed from 0 + end + if size(A{a}, 3) == 4 + % TIFF in CMYK colourspace - convert to RGB + if isfloat(A{a}) + A{a} = A{a} * 255; + else + A{a} = single(A{a}); + end + A{a} = 255 - A{a}; + A{a}(:,:,4) = A{a}(:,:,4) / 255; + A{a} = uint8(A(:,:,1:3) .* A{a}(:,:,[4 4 4])); + end + end + A = cat(4, A{:}); + otherwise + [A, map, alpha] = imread(name); + A = A(:,:,:,1); % Keep only first frame of multi-frame files + if ~isempty(map) + map = uint8(map * 256 - 0.5); % Convert to uint8 for storage + A = reshape(map(uint32(A)+1,:), [size(A) size(map, 2)]); % Assume indexed from 0 + elseif size(A, 3) == 4 + % Assume 4th channel is an alpha matte + alpha = A(:,:,4); + A = A(:,:,1:3); + end +end +end diff --git a/tools/plotting/export_fig/isolate_axes.m b/tools/plotting/export_fig/isolate_axes.m old mode 100755 new mode 100644 index d41f5bf..4e71e69 --- a/tools/plotting/export_fig/isolate_axes.m +++ b/tools/plotting/export_fig/isolate_axes.m @@ -1,3 +1,4 @@ +function fh = isolate_axes(ah, vis) %ISOLATE_AXES Isolate the specified axes in a figure on their own % % Examples: @@ -21,99 +22,109 @@ % Copyright (C) Oliver Woodford 2011-2013 % Thank you to Rosella Blatt for reporting a bug to do with axes in GUIs -% 16/3/2012 Moved copyfig to its own function. Thanks to Bob Fratantonio -% for pointing out that the function is also used in export_fig.m. -% 12/12/12 - Add support for isolating uipanels. Thanks to michael for -% suggesting it. -% 08/10/13 - Bug fix to allchildren suggested by Will Grant (many thanks!). -% 05/12/13 - Bug fix to axes having different units. Thanks to Remington -% Reid for reporting the issue. +% 16/03/12: Moved copyfig to its own function. Thanks to Bob Fratantonio +% for pointing out that the function is also used in export_fig.m +% 12/12/12: Add support for isolating uipanels. Thanks to michael for suggesting it +% 08/10/13: Bug fix to allchildren suggested by Will Grant (many thanks!) +% 05/12/13: Bug fix to axes having different units. Thanks to Remington Reid for reporting +% 21/04/15: Bug fix for exporting uipanels with legend/colorbar on HG1 (reported by Alvaro +% on FEX page as a comment on 24-Apr-2014); standardized indentation & help section +% 22/04/15: Bug fix: legends and colorbars were not exported when exporting axes handle in HG2 -function fh = isolate_axes(ah, vis) -% Make sure we have an array of handles -if ~all(ishandle(ah)) - error('ah must be an array of handles'); -end -% Check that the handles are all for axes or uipanels, and are all in the same figure -fh = ancestor(ah(1), 'figure'); -nAx = numel(ah); -for a = 1:nAx - if ~ismember(get(ah(a), 'Type'), {'axes', 'uipanel'}) - error('All handles must be axes or uipanel handles.'); + % Make sure we have an array of handles + if ~all(ishandle(ah)) + error('ah must be an array of handles'); end - if ~isequal(ancestor(ah(a), 'figure'), fh) - error('Axes must all come from the same figure.'); + % Check that the handles are all for axes or uipanels, and are all in the same figure + fh = ancestor(ah(1), 'figure'); + nAx = numel(ah); + for a = 1:nAx + if ~ismember(get(ah(a), 'Type'), {'axes', 'uipanel'}) + error('All handles must be axes or uipanel handles.'); + end + if ~isequal(ancestor(ah(a), 'figure'), fh) + error('Axes must all come from the same figure.'); + end end -end -% Tag the objects so we can find them in the copy -old_tag = get(ah, 'Tag'); -if nAx == 1 - old_tag = {old_tag}; -end -set(ah, 'Tag', 'ObjectToCopy'); -% Create a new figure exactly the same as the old one -fh = copyfig(fh); %copyobj(fh, 0); -if nargin < 2 || ~vis - set(fh, 'Visible', 'off'); -end -% Reset the object tags -for a = 1:nAx - set(ah(a), 'Tag', old_tag{a}); -end -% Find the objects to save -ah = findall(fh, 'Tag', 'ObjectToCopy'); -if numel(ah) ~= nAx - close(fh); - error('Incorrect number of objects found.'); -end -% Set the axes tags to what they should be -for a = 1:nAx - set(ah(a), 'Tag', old_tag{a}); -end -% Keep any legends and colorbars which overlap the subplots -lh = findall(fh, 'Type', 'axes', '-and', {'Tag', 'legend', '-or', 'Tag', 'Colorbar'}); -nLeg = numel(lh); -if nLeg > 0 - set([ah(:); lh(:)], 'Units', 'normalized'); - ax_pos = get(ah, 'OuterPosition'); - if nAx > 1 - ax_pos = cell2mat(ax_pos(:)); + % Tag the objects so we can find them in the copy + old_tag = get(ah, 'Tag'); + if nAx == 1 + old_tag = {old_tag}; + end + set(ah, 'Tag', 'ObjectToCopy'); + % Create a new figure exactly the same as the old one + fh = copyfig(fh); %copyobj(fh, 0); + if nargin < 2 || ~vis + set(fh, 'Visible', 'off'); + end + % Reset the object tags + for a = 1:nAx + set(ah(a), 'Tag', old_tag{a}); end - ax_pos(:,3:4) = ax_pos(:,3:4) + ax_pos(:,1:2); - leg_pos = get(lh, 'OuterPosition'); - if nLeg > 1; - leg_pos = cell2mat(leg_pos); + % Find the objects to save + ah = findall(fh, 'Tag', 'ObjectToCopy'); + if numel(ah) ~= nAx + close(fh); + error('Incorrect number of objects found.'); end - leg_pos(:,3:4) = leg_pos(:,3:4) + leg_pos(:,1:2); - ax_pos = shiftdim(ax_pos, -1); - % Overlap test - M = bsxfun(@lt, leg_pos(:,1), ax_pos(:,:,3)) & ... - bsxfun(@lt, leg_pos(:,2), ax_pos(:,:,4)) & ... - bsxfun(@gt, leg_pos(:,3), ax_pos(:,:,1)) & ... - bsxfun(@gt, leg_pos(:,4), ax_pos(:,:,2)); - ah = [ah; lh(any(M, 2))]; + % Set the axes tags to what they should be + for a = 1:nAx + set(ah(a), 'Tag', old_tag{a}); + end + % Keep any legends and colorbars which overlap the subplots + % Note: in HG1 these are axes objects; in HG2 they are separate objects, therefore we + % don't test for the type, only the tag (hopefully nobody but Matlab uses them!) + lh = findall(fh, 'Tag', 'legend', '-or', 'Tag', 'Colorbar'); + nLeg = numel(lh); + if nLeg > 0 + set([ah(:); lh(:)], 'Units', 'normalized'); + try + ax_pos = get(ah, 'OuterPosition'); % axes and figures have the OuterPosition property + catch + ax_pos = get(ah, 'Position'); % uipanels only have Position, not OuterPosition + end + if nAx > 1 + ax_pos = cell2mat(ax_pos(:)); + end + ax_pos(:,3:4) = ax_pos(:,3:4) + ax_pos(:,1:2); + try + leg_pos = get(lh, 'OuterPosition'); + catch + leg_pos = get(lh, 'Position'); % No OuterPosition in HG2, only in HG1 + end + if nLeg > 1; + leg_pos = cell2mat(leg_pos); + end + leg_pos(:,3:4) = leg_pos(:,3:4) + leg_pos(:,1:2); + ax_pos = shiftdim(ax_pos, -1); + % Overlap test + M = bsxfun(@lt, leg_pos(:,1), ax_pos(:,:,3)) & ... + bsxfun(@lt, leg_pos(:,2), ax_pos(:,:,4)) & ... + bsxfun(@gt, leg_pos(:,3), ax_pos(:,:,1)) & ... + bsxfun(@gt, leg_pos(:,4), ax_pos(:,:,2)); + ah = [ah; lh(any(M, 2))]; + end + % Get all the objects in the figure + axs = findall(fh); + % Delete everything except for the input objects and associated items + delete(axs(~ismember(axs, [ah; allchildren(ah); allancestors(ah)]))); end -% Get all the objects in the figure -axs = findall(fh); -% Delete everything except for the input objects and associated items -delete(axs(~ismember(axs, [ah; allchildren(ah); allancestors(ah)]))); -return function ah = allchildren(ah) -ah = findall(ah); -if iscell(ah) - ah = cell2mat(ah); + ah = findall(ah); + if iscell(ah) + ah = cell2mat(ah); + end + ah = ah(:); end -ah = ah(:); -return function ph = allancestors(ah) -ph = []; -for a = 1:numel(ah) - h = get(ah(a), 'parent'); - while h ~= 0 - ph = [ph; h]; - h = get(h, 'parent'); + ph = []; + for a = 1:numel(ah) + h = get(ah(a), 'parent'); + while h ~= 0 + ph = [ph; h]; + h = get(h, 'parent'); + end end end -return \ No newline at end of file diff --git a/tools/plotting/export_fig/license.txt b/tools/plotting/export_fig/license.txt deleted file mode 100644 index bf48297..0000000 --- a/tools/plotting/export_fig/license.txt +++ /dev/null @@ -1,24 +0,0 @@ -Copyright (c) 2013, Oliver Woodford -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * 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 OWNER 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. diff --git a/tools/plotting/export_fig/pdf2eps.m b/tools/plotting/export_fig/pdf2eps.m old mode 100755 new mode 100644 index f627bfa..8010b2a --- a/tools/plotting/export_fig/pdf2eps.m +++ b/tools/plotting/export_fig/pdf2eps.m @@ -7,45 +7,49 @@ % % This function requires that you have pdftops, from the Xpdf suite of % functions, installed on your system. This can be downloaded from: -% http://www.foolabs.com/xpdf +% http://xpdfreader.com % -%IN: +% Inputs: % source - filename of the source pdf file to convert. The filename is % assumed to already have the extension ".pdf". % dest - filename of the destination eps file. The filename is assumed to % already have the extension ".eps". -% Copyright (C) Oliver Woodford 2009-2010 +% Copyright (C) Oliver Woodford 2009-2010, Yair Altman 2015- % Thanks to Aldebaro Klautau for reporting a bug when saving to % non-existant directories. +% 22/09/2018 - Xpdf website changed to xpdfreader.com + function pdf2eps(source, dest) -% Construct the options string for pdftops -options = ['-q -paper match -eps -level2 "' source '" "' dest '"']; -% Convert to eps using pdftops -[status message] = pdftops(options); -% Check for error -if status - % Report error - if isempty(message) - error('Unable to generate eps. Check destination directory is writable.'); - else - error(message); + % Construct the options string for pdftops + options = ['-q -paper match -eps -level2 "' source '" "' dest '"']; + + % Convert to eps using pdftops + [status, message] = pdftops(options); + + % Check for error + if status + % Report error + if isempty(message) + error('Unable to generate eps. Check destination directory is writable.'); + else + error(message); + end end -end -% Fix the DSC error created by pdftops -fid = fopen(dest, 'r+'); -if fid == -1 - % Cannot open the file - return -end -fgetl(fid); % Get the first line -str = fgetl(fid); % Get the second line -if strcmp(str(1:min(13, end)), '% Produced by') - fseek(fid, -numel(str)-1, 'cof'); - fwrite(fid, '%'); % Turn ' ' into '%' -end -fclose(fid); -return + % Fix the DSC error created by pdftops + fid = fopen(dest, 'r+'); + if fid == -1 + % Cannot open the file + return + end + fgetl(fid); % Get the first line + str = fgetl(fid); % Get the second line + if strcmp(str(1:min(13, end)), '% Produced by') + fseek(fid, -numel(str)-1, 'cof'); + fwrite(fid, '%'); % Turn ' ' into '%' + end + fclose(fid); +end diff --git a/tools/plotting/export_fig/pdftops.m b/tools/plotting/export_fig/pdftops.m old mode 100755 new mode 100644 index 4e8dde3..04ad831 --- a/tools/plotting/export_fig/pdftops.m +++ b/tools/plotting/export_fig/pdftops.m @@ -11,11 +11,10 @@ % Once found, the executable is called with the input command string. % % This function requires that you have pdftops (from the Xpdf package) -% installed on your system. You can download this from: -% http://www.foolabs.com/xpdf +% installed on your system. You can download this from: http://xpdfreader.com % % IN: -% cmd - Command string to be passed into pdftops. +% cmd - Command string to be passed into pdftops (e.g. '-help'). % % OUT: % status - 0 iff command ran without problem. @@ -23,85 +22,147 @@ % Copyright: Oliver Woodford, 2009-2010 -% Thanks to Jonas Dorn for the fix for the title of the uigetdir window on -% Mac OS. -% Thanks to Christoph Hertel for pointing out a bug in check_xpdf_path -% under linux. +% Thanks to Jonas Dorn for the fix for the title of the uigetdir window on Mac OS. +% Thanks to Christoph Hertel for pointing out a bug in check_xpdf_path under linux. +% 23/01/2014 - Add full path to pdftops.txt in warning. +% 27/05/2015 - Fixed alert in case of missing pdftops; fixed code indentation +% 02/05/2016 - Added possible error explanation suggested by Michael Pacer (issue #137) +% 02/05/2016 - Search additional possible paths suggested by Jonas Stein (issue #147) +% 03/05/2016 - Display the specific error message if pdftops fails for some reason (issue #148) +% 22/09/2018 - Xpdf website changed to xpdfreader.com; improved popup logic -% Call pdftops -[varargout{1:nargout}] = system(sprintf('"%s" %s', xpdf_path, cmd)); -return + % Call pdftops + [varargout{1:nargout}] = system([xpdf_command(xpdf_path()) cmd]); +end function path_ = xpdf_path -% Return a valid path -% Start with the currently set path -path_ = user_string('pdftops'); -% Check the path works -if check_xpdf_path(path_) - return -end -% Check whether the binary is on the path -if ispc - bin = 'pdftops.exe'; -else - bin = 'pdftops'; -end -if check_store_xpdf_path(bin) - path_ = bin; - return -end -% Search the obvious places -if ispc - path_ = 'C:\Program Files\xpdf\pdftops.exe'; -else - path_ = '/usr/local/bin/pdftops'; -end -if check_store_xpdf_path(path_) - return -end -% Ask the user to enter the path -while 1 - if strncmp(computer,'MAC',3) % Is a Mac - % Give separate warning as the uigetdir dialogue box doesn't have a - % title - uiwait(warndlg('Pdftops not found. Please locate the program, or install xpdf-tools from http://users.phg-online.de/tk/MOSXS/.')) + % Return a valid path + % Start with the currently set path + path_ = user_string('pdftops'); + % Check the path works + if check_xpdf_path(path_) + return end - base = uigetdir('/', 'Pdftops not found. Please locate the program.'); - if isequal(base, 0) - % User hit cancel or closed window - break; + % Check whether the binary is on the path + if ispc + bin = 'pdftops.exe'; + else + bin = 'pdftops'; end - base = [base filesep]; - bin_dir = {'', ['bin' filesep], ['lib' filesep]}; - for a = 1:numel(bin_dir) - path_ = [base bin_dir{a} bin]; - if exist(path_, 'file') == 2 - break; + if check_store_xpdf_path(bin) + path_ = bin; + return + end + % Search the obvious places + if ispc + paths = {'C:\Program Files\xpdf\pdftops.exe', 'C:\Program Files (x86)\xpdf\pdftops.exe'}; + else + paths = {'/usr/bin/pdftops', '/usr/local/bin/pdftops'}; + end + for a = 1:numel(paths) + path_ = paths{a}; + if check_store_xpdf_path(path_) + return end end - if check_store_xpdf_path(path_) - return + + % Ask the user to enter the path + errMsg1 = 'Pdftops not found. Please locate the program, or install xpdf-tools from '; + url1 = 'http://xpdfreader.com/download.html'; %='http://foolabs.com/xpdf'; + fprintf(2, '%s\n', [errMsg1 '' url1 '']); + errMsg1 = [errMsg1 url1]; + %if strncmp(computer,'MAC',3) % Is a Mac + % % Give separate warning as the MacOS uigetdir dialogue box doesn't have a title + % uiwait(warndlg(errMsg1)) + %end + + % Provide an alternative possible explanation as per issue #137 + errMsg2 = 'If you have pdftops installed, perhaps Matlab is shaddowing it as described in '; + url2 = 'https://github.com/altmany/export_fig/issues/137'; + fprintf(2, '%s\n', [errMsg2 'issue #137']); + errMsg2 = [errMsg2 url1]; + + state = 1; + while 1 + if state + option1 = 'Install pdftops'; + else + option1 = 'Issue #137'; + end + answer = questdlg({errMsg1,'',errMsg2},'Pdftops error',option1,'Locate pdftops','Cancel','Cancel'); + drawnow; % prevent a Matlab hang: http://undocumentedmatlab.com/blog/solving-a-matlab-hang-problem + switch answer + case 'Install pdftops' + web('-browser',url1); + state = 0; + case 'Issue #137' + web('-browser',url2); + state = 1; + case 'Locate pdftops' + base = uigetdir('/', errMsg1); + if isequal(base, 0) + % User hit cancel or closed window + break + end + base = [base filesep]; %#ok + bin_dir = {'', ['bin' filesep], ['lib' filesep]}; + for a = 1:numel(bin_dir) + path_ = [base bin_dir{a} bin]; + if exist(path_, 'file') == 2 + break + end + end + if check_store_xpdf_path(path_) + return + end + + otherwise % User hit Cancel or closed window + break + end end + error('pdftops executable not found.'); end -error('pdftops executable not found.'); function good = check_store_xpdf_path(path_) -% Check the path is valid -good = check_xpdf_path(path_); -if ~good - return -end -% Update the current default path to the path found -if ~user_string('pdftops', path_) - warning('Path to pdftops executable could not be saved. Enter it manually in pdftops.txt.'); - return + % Check the path is valid + good = check_xpdf_path(path_); + if ~good + return + end + % Update the current default path to the path found + if ~user_string('pdftops', path_) + warning('Path to pdftops executable could not be saved. Enter it manually in %s.', fullfile(fileparts(which('user_string.m')), '.ignore', 'pdftops.txt')); + return + end end -return function good = check_xpdf_path(path_) -% Check the path is valid -[good message] = system(sprintf('"%s" -h', path_)); -% system returns good = 1 even when the command runs -% Look for something distinct in the help text -good = ~isempty(strfind(message, 'PostScript')); -return \ No newline at end of file + % Check the path is valid + [good, message] = system([xpdf_command(path_) '-h']); %#ok + % system returns good = 1 even when the command runs + % Look for something distinct in the help text + good = ~isempty(strfind(message, 'PostScript')); %#ok + + % Display the error message if the pdftops executable exists but fails for some reason + if ~good && exist(path_,'file') % file exists but generates an error + fprintf('Error running %s:\n', path_); + fprintf(2,'%s\n\n',message); + end +end + +function cmd = xpdf_command(path_) + % Initialize any required system calls before calling ghostscript + % TODO: in Unix/Mac, find a way to determine whether to use "export" (bash) or "setenv" (csh/tcsh) + shell_cmd = ''; + if isunix + % Avoids an error on Linux with outdated MATLAB lib files + % R20XXa/bin/glnxa64/libtiff.so.X + % R20XXa/sys/os/glnxa64/libstdc++.so.X + shell_cmd = 'export LD_LIBRARY_PATH=""; '; + end + if ismac + shell_cmd = 'export DYLD_LIBRARY_PATH=""; '; + end + % Construct the command string + cmd = sprintf('%s"%s" ', shell_cmd, path_); +end diff --git a/tools/plotting/export_fig/print2array.m b/tools/plotting/export_fig/print2array.m old mode 100755 new mode 100644 index 5005f9f..287989b --- a/tools/plotting/export_fig/print2array.m +++ b/tools/plotting/export_fig/print2array.m @@ -1,3 +1,4 @@ +function [A, bcol] = print2array(fig, res, renderer, gs_options) %PRINT2ARRAY Exports a figure to an image array % % Examples: @@ -5,6 +6,7 @@ % A = print2array(figure_handle) % A = print2array(figure_handle, resolution) % A = print2array(figure_handle, resolution, renderer) +% A = print2array(figure_handle, resolution, renderer, gs_options) % [A bcol] = print2array(...) % % This function outputs a bitmap image of the given figure, at the desired @@ -19,123 +21,207 @@ % resolution. Default: 1. % renderer - string containing the renderer paramater to be passed to % print. Default: '-opengl'. +% gs_options - optional ghostscript options (e.g.: '-dNoOutputFonts'). If +% multiple options are needed, enclose in call array: {'-a','-b'} % % OUT: % A - MxNx3 uint8 image of the figure. % bcol - 1x3 uint8 vector of the background color -% Copyright (C) Oliver Woodford 2008-2012 - +% Copyright (C) Oliver Woodford 2008-2014, Yair Altman 2015- +%{ % 05/09/11: Set EraseModes to normal when using opengl or zbuffer -% renderers. Thanks to Pawel Kocieniewski for reporting the -% issue. -% 21/09/11: Bug fix: unit8 -> uint8! Thanks to Tobias Lamour for reporting -% the issue. -% 14/11/11: Bug fix: stop using hardcopy(), as it interfered with figure -% size and erasemode settings. Makes it a bit slower, but more -% reliable. Thanks to Phil Trinh and Meelis Lootus for reporting -% the issues. +% renderers. Thanks to Pawel Kocieniewski for reporting the issue. +% 21/09/11: Bug fix: unit8 -> uint8! Thanks to Tobias Lamour for reporting it. +% 14/11/11: Bug fix: stop using hardcopy(), as it interfered with figure size +% and erasemode settings. Makes it a bit slower, but more reliable. +% Thanks to Phil Trinh and Meelis Lootus for reporting the issues. % 09/12/11: Pass font path to ghostscript. % 27/01/12: Bug fix affecting painters rendering tall figures. Thanks to % Ken Campbell for reporting it. -% 03/04/12: Bug fix to median input. Thanks to Andy Matthews for reporting -% it. +% 03/04/12: Bug fix to median input. Thanks to Andy Matthews for reporting it. % 26/10/12: Set PaperOrientation to portrait. Thanks to Michael Watts for % reporting the issue. +% 26/02/15: If temp dir is not writable, use the current folder for temp +% EPS/TIF files (Javier Paredes) +% 27/02/15: Display suggested workarounds to internal print() error (issue #16) +% 28/02/15: Enable users to specify optional ghostscript options (issue #36) +% 10/03/15: Fixed minor warning reported by Paul Soderlind; fixed code indentation +% 28/05/15: Fixed issue #69: patches with LineWidth==0.75 appear wide (internal bug in Matlab's print() func) +% 07/07/15: Fixed issue #83: use numeric handles in HG1 +% 11/12/16: Fixed cropping issue reported by Harry D. +% 29/09/18: Fixed issue #254: error in print2array>read_tif_img +%} -function [A, bcol] = print2array(fig, res, renderer) -% Generate default input arguments, if needed -if nargin < 2 - res = 1; - if nargin < 1 - fig = gcf; + % Generate default input arguments, if needed + if nargin < 2 + res = 1; + if nargin < 1 + fig = gcf; + end end -end -% Warn if output is large -old_mode = get(fig, 'Units'); -set(fig, 'Units', 'pixels'); -px = get(fig, 'Position'); -set(fig, 'Units', old_mode); -npx = prod(px(3:4)*res)/1e6; -if npx > 30 - % 30M pixels or larger! - warning('MATLAB:LargeImage', 'print2array generating a %.1fM pixel image. This could be slow and might also cause memory problems.', npx); -end -% Retrieve the background colour -bcol = get(fig, 'Color'); -% Set the resolution parameter -res_str = ['-r' num2str(ceil(get(0, 'ScreenPixelsPerInch')*res))]; -% Generate temporary file name -tmp_nam = [tempname '.tif']; -if nargin > 2 && strcmp(renderer, '-painters') - % Print to eps file - tmp_eps = [tempname '.eps']; - print2eps(tmp_eps, fig, renderer, '-loose'); + % Warn if output is large + old_mode = get(fig, 'Units'); + set(fig, 'Units', 'pixels'); + px = get(fig, 'Position'); + set(fig, 'Units', old_mode); + npx = prod(px(3:4)*res)/1e6; + if npx > 30 + % 30M pixels or larger! + warning('MATLAB:LargeImage', 'print2array generating a %.1fM pixel image. This could be slow and might also cause memory problems.', npx); + end + % Retrieve the background colour + bcol = get(fig, 'Color'); + % Set the resolution parameter + res_str = ['-r' num2str(ceil(get(0, 'ScreenPixelsPerInch')*res))]; + % Generate temporary file name + tmp_nam = [tempname '.tif']; try - % Initialize the command to export to tiff using ghostscript - cmd_str = ['-dEPSCrop -q -dNOPAUSE -dBATCH ' res_str ' -sDEVICE=tiff24nc']; - % Set the font path - fp = font_path(); - if ~isempty(fp) - cmd_str = [cmd_str ' -sFONTPATH="' fp '"']; - end - % Add the filenames - cmd_str = [cmd_str ' -sOutputFile="' tmp_nam '" "' tmp_eps '"']; - % Execute the ghostscript command - ghostscript(cmd_str); - catch me - % Delete the intermediate file - delete(tmp_eps); - rethrow(me); + % Ensure that the temp dir is writable (Javier Paredes 26/2/15) + fid = fopen(tmp_nam,'w'); + fwrite(fid,1); + fclose(fid); + delete(tmp_nam); % cleanup + isTempDirOk = true; + catch + % Temp dir is not writable, so use the current folder + [dummy,fname,fext] = fileparts(tmp_nam); %#ok + fpath = pwd; + tmp_nam = fullfile(fpath,[fname fext]); + isTempDirOk = false; end - % Delete the intermediate file - delete(tmp_eps); - % Read in the generated bitmap - A = imread(tmp_nam); - % Delete the temporary bitmap file - delete(tmp_nam); - % Set border pixels to the correct colour - if isequal(bcol, 'none') - bcol = []; - elseif isequal(bcol, [1 1 1]) - bcol = uint8([255 255 255]); + % Enable users to specify optional ghostscript options (issue #36) + if nargin > 3 && ~isempty(gs_options) + if iscell(gs_options) + gs_options = sprintf(' %s',gs_options{:}); + elseif ~ischar(gs_options) + error('gs_options input argument must be a string or cell-array of strings'); + else + gs_options = [' ' gs_options]; + end else - for l = 1:size(A, 2) - if ~all(reshape(A(:,l,:) == 255, [], 1)) - break; + gs_options = ''; + end + if nargin > 2 && strcmp(renderer, '-painters') + % First try to print directly to tif file + try + % Print the file into a temporary TIF file and read it into array A + [A, err, ex] = read_tif_img(fig, res_str, renderer, tmp_nam); + if err, rethrow(ex); end + catch % error - try to print to EPS and then using Ghostscript to TIF + % Print to eps file + if isTempDirOk + tmp_eps = [tempname '.eps']; + else + tmp_eps = fullfile(fpath,[fname '.eps']); end - end - for r = size(A, 2):-1:l - if ~all(reshape(A(:,r,:) == 255, [], 1)) - break; + print2eps(tmp_eps, fig, 0, renderer, '-loose'); + try + % Initialize the command to export to tiff using ghostscript + cmd_str = ['-dEPSCrop -q -dNOPAUSE -dBATCH ' res_str ' -sDEVICE=tiff24nc']; + % Set the font path + fp = font_path(); + if ~isempty(fp) + cmd_str = [cmd_str ' -sFONTPATH="' fp '"']; + end + % Add the filenames + cmd_str = [cmd_str ' -sOutputFile="' tmp_nam '" "' tmp_eps '"' gs_options]; + % Execute the ghostscript command + ghostscript(cmd_str); + catch me + % Delete the intermediate file + delete(tmp_eps); + rethrow(me); end + % Delete the intermediate file + delete(tmp_eps); + % Read in the generated bitmap + A = imread(tmp_nam); + % Delete the temporary bitmap file + delete(tmp_nam); end - for t = 1:size(A, 1) - if ~all(reshape(A(t,:,:) == 255, [], 1)) - break; + % Set border pixels to the correct colour + if isequal(bcol, 'none') + bcol = []; + elseif isequal(bcol, [1 1 1]) + bcol = uint8([255 255 255]); + else + for l = 1:size(A, 2) + if ~all(reshape(A(:,l,:) == 255, [], 1)) + break; + end end - end - for b = size(A, 1):-1:t - if ~all(reshape(A(b,:,:) == 255, [], 1)) - break; + for r = size(A, 2):-1:l + if ~all(reshape(A(:,r,:) == 255, [], 1)) + break; + end + end + for t = 1:size(A, 1) + if ~all(reshape(A(t,:,:) == 255, [], 1)) + break; + end + end + for b = size(A, 1):-1:t + if ~all(reshape(A(b,:,:) == 255, [], 1)) + break; + end end + bcol = uint8(median(single([reshape(A(:,[l r],:), [], size(A, 3)); reshape(A([t b],:,:), [], size(A, 3))]), 1)); + for c = 1:size(A, 3) + A(:,[1:l-1, r+1:end],c) = bcol(c); + A([1:t-1, b+1:end],:,c) = bcol(c); + end + end + else + if nargin < 3 + renderer = '-opengl'; + end + % Print the file into a temporary TIF file and read it into array A + [A, err, ex] = read_tif_img(fig, res_str, renderer, tmp_nam); + % Throw any error that occurred + if err + % Display suggested workarounds to internal print() error (issue #16) + fprintf(2, 'An error occured with Matlab''s builtin print function.\nTry setting the figure Renderer to ''painters'' or use opengl(''software'').\n\n'); + rethrow(ex); end - bcol = uint8(median(single([reshape(A(:,[l r],:), [], size(A, 3)); reshape(A([t b],:,:), [], size(A, 3))]), 1)); - for c = 1:size(A, 3) - A(:,[1:l-1, r+1:end],c) = bcol(c); - A([1:t-1, b+1:end],:,c) = bcol(c); + % Set the background color + if isequal(bcol, 'none') + bcol = []; + else + bcol = bcol * 255; + if isequal(bcol, round(bcol)) + bcol = uint8(bcol); + else + bcol = squeeze(A(1,1,:)); + end end end -else - if nargin < 3 - renderer = '-opengl'; + % Check the output size is correct + if isequal(res, round(res)) + px = round([px([4 3])*res 3]); % round() to avoid an indexing warning below + if ~isequal(size(A), px) + % Correct the output size + A = A(1:min(end,px(1)),1:min(end,px(2)),:); + end end +end + +% Function to create a TIF image of the figure and read it into an array +function [A, err, ex] = read_tif_img(fig, res_str, renderer, tmp_nam) + A = []; % fix for issue #254 err = false; - % Set paper size - old_pos_mode = get(fig, 'PaperPositionMode'); + ex = []; + % Temporarily set the paper size + old_pos_mode = get(fig, 'PaperPositionMode'); old_orientation = get(fig, 'PaperOrientation'); - set(fig, 'PaperPositionMode', 'auto', 'PaperOrientation', 'portrait'); + set(fig, 'PaperPositionMode','auto', 'PaperOrientation','portrait'); try + % Workaround for issue #69: patches with LineWidth==0.75 appear wide (internal bug in Matlab's print() function) + fp = []; % in case we get an error below + fp = findall(fig, 'Type','patch', 'LineWidth',0.75); + set(fp, 'LineWidth',0.5); + % Fix issue #83: use numeric handles in HG1 + if ~using_hg2(fig), fig = double(fig); end % Print to tiff file print(fig, renderer, res_str, '-dtiff', tmp_nam); % Read in the printed file @@ -145,54 +231,31 @@ catch ex err = true; end - % Reset paper size - set(fig, 'PaperPositionMode', old_pos_mode, 'PaperOrientation', old_orientation); - % Throw any error that occurred - if err - rethrow(ex); - end - % Set the background color - if isequal(bcol, 'none') - bcol = []; - else - bcol = bcol * 255; - if isequal(bcol, round(bcol)) - bcol = uint8(bcol); - else - bcol = squeeze(A(1,1,:)); - end - end + set(fp, 'LineWidth',0.75); % restore original figure appearance + % Reset the paper size + set(fig, 'PaperPositionMode',old_pos_mode, 'PaperOrientation',old_orientation); end -% Check the output size is correct -if isequal(res, round(res)) - px = [px([4 3])*res 3]; - if ~isequal(size(A), px) - % Correct the output size - A = A(1:min(end,px(1)),1:min(end,px(2)),:); - end -end -return % Function to return (and create, where necessary) the font path function fp = font_path() -fp = user_string('gs_font_path'); -if ~isempty(fp) - return -end -% Create the path -% Start with the default path -fp = getenv('GS_FONTPATH'); -% Add on the typical directories for a given OS -if ispc + fp = user_string('gs_font_path'); if ~isempty(fp) - fp = [fp ';']; + return end - fp = [fp getenv('WINDIR') filesep 'Fonts']; -else - if ~isempty(fp) - fp = [fp ':']; + % Create the path + % Start with the default path + fp = getenv('GS_FONTPATH'); + % Add on the typical directories for a given OS + if ispc + if ~isempty(fp) + fp = [fp ';']; + end + fp = [fp getenv('WINDIR') filesep 'Fonts']; + else + if ~isempty(fp) + fp = [fp ':']; + end + fp = [fp '/usr/share/fonts:/usr/local/share/fonts:/usr/share/fonts/X11:/usr/local/share/fonts/X11:/usr/share/fonts/truetype:/usr/local/share/fonts/truetype']; end - fp = [fp '/usr/share/fonts:/usr/local/share/fonts:/usr/share/fonts/X11:/usr/local/share/fonts/X11:/usr/share/fonts/truetype:/usr/local/share/fonts/truetype']; + user_string('gs_font_path', fp); end -user_string('gs_font_path', fp); -return diff --git a/tools/plotting/export_fig/print2eps.m b/tools/plotting/export_fig/print2eps.m old mode 100755 new mode 100644 index 931b4b4..8dd9a1f --- a/tools/plotting/export_fig/print2eps.m +++ b/tools/plotting/export_fig/print2eps.m @@ -1,35 +1,51 @@ +function print2eps(name, fig, export_options, varargin) %PRINT2EPS Prints figures to eps with improved line styles % % Examples: % print2eps filename % print2eps(filename, fig_handle) -% print2eps(filename, fig_handle, options) +% print2eps(filename, fig_handle, export_options) +% print2eps(filename, fig_handle, export_options, print_options) % % This function saves a figure as an eps file, with two improvements over % MATLAB's print command. First, it improves the line style, making dashed -% lines more like those on screen and giving grid lines their own dotted -% style. Secondly, it substitutes original font names back into the eps -% file, where these have been changed by MATLAB, for up to 11 different -% fonts. +% lines more like those on screen and giving grid lines a dotted line style. +% Secondly, it substitutes original font names back into the eps file, +% where these have been changed by MATLAB, for up to 11 different fonts. % %IN: % filename - string containing the name (optionally including full or % relative path) of the file the figure is to be saved as. A % ".eps" extension is added if not there already. If a path is % not specified, the figure is saved in the current directory. -% fig_handle - The handle of the figure to be saved. Default: gcf. -% options - Additional parameter strings to be passed to print. +% fig_handle - The handle of the figure to be saved. Default: gcf(). +% export_options - array or struct of optional scalar values: +% bb_padding - Scalar value of amount of padding to add to border around +% the cropped image, in points (if >1) or percent (if <1). +% Can be negative as well as positive; Default: 0 +% crop - Cropping flag. Deafult: 0 +% fontswap - Whether to swap non-default fonts in figure. Default: true +% font_space - Character used to separate font-name terms in the EPS output +% e.g. "Courier New" => "Courier-New". Default: '' +% (available only via the struct alternative) +% renderer - Renderer used to generate bounding-box. Default: 'opengl' +% (available only via the struct alternative) +% crop_amounts - 4-element vector of crop amounts: [top,right,bottom,left] +% (available only via the struct alternative) +% print_options - Additional parameter strings to be passed to the print command -% Copyright (C) Oliver Woodford 2008-2013 +%{ +% Copyright (C) Oliver Woodford 2008-2014, Yair Altman 2015- % The idea of editing the EPS file to change line styles comes from Jiro % Doke's FIXPSLINESTYLE (fex id: 17928) % The idea of changing dash length with line width came from comments on % fex id: 5743, but the implementation is mine :) - -% 14/11/2011: Fix a MATLAB bug rendering black or white text incorrectly. -% Thanks to Mathieu Morlighem for reporting the issue and -% obtaining a fix from TMW. +%} +%{ +% 14/11/11: Fix a MATLAB bug rendering black or white text incorrectly. +% Thanks to Mathieu Morlighem for reporting the issue and +% obtaining a fix from TMW. % 08/12/11: Added ability to correct fonts. Several people have requested % this at one time or another, and also pointed me to printeps % (fex id: 7501), so thank you to them. My implementation (which @@ -49,173 +65,541 @@ % for reporting the issue. % 22/03/13: Extend font swapping to axes labels. Thanks to Rasmus Ischebeck % for reporting the issue. -% 23/07/13: Bug fix to font swapping. Thank to George for reporting the +% 23/07/13: Bug fix to font swapping. Thanks to George for reporting the % issue. % 13/08/13: Fix MATLAB feature of not exporting white lines correctly. -% Thanks to Sebastian Heßlinger for reporting it. - -function print2eps(name, fig, varargin) -options = {'-depsc2'}; -if nargin < 2 - fig = gcf; -elseif nargin > 2 - options = [options varargin]; -end -% Construct the filename -if numel(name) < 5 || ~strcmpi(name(end-3:end), '.eps') - name = [name '.eps']; % Add the missing extension -end -% Find all the used fonts in the figure -font_handles = findall(fig, '-property', 'FontName'); -fonts = get(font_handles, 'FontName'); -if ~iscell(fonts) - fonts = {fonts}; -end -% Map supported font aliases onto the correct name -fontsl = lower(fonts); -for a = 1:numel(fonts) - f = fontsl{a}; - f(f==' ') = []; - switch f - case {'times', 'timesnewroman', 'times-roman'} - fontsl{a} = 'times-roman'; - case {'arial', 'helvetica'} - fontsl{a} = 'helvetica'; - case {'newcenturyschoolbook', 'newcenturyschlbk'} - fontsl{a} = 'newcenturyschlbk'; - otherwise +% Thanks to Sebastian Hesslinger for reporting it. +% 24/02/15: Fix for Matlab R2014b bug (issue #31): LineWidths<0.75 are not +% set in the EPS (default line width is used) +% 25/02/15: Fixed issue #32: BoundingBox problem caused uncropped EPS/PDF files +% 05/03/15: Fixed issue #43: Inability to perform EPS file post-processing +% 06/03/15: Improved image padding & cropping thanks to Oscar Hartogensis +% 21/03/15: Fixed edge-case of missing handles having a 'FontName' property +% 26/03/15: Attempt to fix issue #45: white lines in subplots do not print correctly +% 27/03/15: Attempt to fix issue #44: white artifact lines appearing in patch exports +% 30/03/15: Fixed issue #52: improved performance on HG2 (R2014b+) +% 09/04/15: Comment blocks consolidation and minor code cleanup (no real code change) +% 12/04/15: Fixed issue #56: bad cropping +% 14/04/15: Workaround for issue #45: lines in image subplots are exported in invalid color +% 07/07/15: Added option to avoid font-swapping in EPS/PDF +% 07/07/15: Fixed issue #83: use numeric handles in HG1 +% 22/07/15: Fixed issue #91 (thanks to Carlos Moffat) +% 28/09/15: Fixed issue #108 (thanks to JacobD10) +% 01/11/15: Fixed issue #112: optional renderer for bounding-box computation (thanks to Jesús Pestana Puerta) +% 21/02/16: Enabled specifying non-automated crop amounts +% 22/02/16: Better support + backward compatibility for transparency (issue #108) +% 10/06/16: Fixed issue #159: text handles get cleared by Matlab in the print() command +% 12/06/16: Improved the fix for issue #159 (in the previous commit) +% 12/06/16: Fixed issue #158: transparent patch color in PDF/EPS +% 18/09/17: Fixed issue #194: incorrect fonts in EPS/PDF output +% 18/09/17: Fixed issue #195: relaxed too-tight cropping in EPS/PDF +% 14/11/17: Workaround for issue #211: dashed/dotted lines in 3D axes appear solid +% 15/11/17: Updated issue #211: only set SortMethod='ChildOrder' in HG2, and when it looks the same onscreen; support multiple figure axes +% 18/11/17: Fixed issue #225: transparent/translucent dashed/dotted lines appear solid in EPS/PDF +% 24/03/18: Fixed issue #239: black title meshes with temporary black background figure bgcolor, causing bad cropping +%} + + options = {'-loose'}; + if nargin > 3 + options = [options varargin]; + elseif nargin < 3 + export_options = 0; + if nargin < 2 + fig = gcf(); + end end -end -fontslu = unique(fontsl); -% Determine the font swap table -matlab_fonts = {'Helvetica', 'Times-Roman', 'Palatino', 'Bookman', 'Helvetica-Narrow', 'Symbol', ... - 'AvantGarde', 'NewCenturySchlbk', 'Courier', 'ZapfChancery', 'ZapfDingbats'}; -matlab_fontsl = lower(matlab_fonts); -require_swap = find(~ismember(fontslu, matlab_fontsl)); -unused_fonts = find(~ismember(matlab_fontsl, fontslu)); -font_swap = cell(3, min(numel(require_swap), numel(unused_fonts))); -fonts_new = fonts; -for a = 1:size(font_swap, 2) - font_swap{1,a} = find(strcmp(fontslu{require_swap(a)}, fontsl)); - font_swap{2,a} = matlab_fonts{unused_fonts(a)}; - font_swap{3,a} = fonts{font_swap{1,a}(1)}; - fonts_new(font_swap{1,a}) = {font_swap{2,a}}; -end -% Swap the fonts -if ~isempty(font_swap) - fonts_size = get(font_handles, 'FontSize'); - if iscell(fonts_size) - fonts_size = cell2mat(fonts_size); - end - M = false(size(font_handles)); - % Loop because some changes may not stick first time, due to listeners - c = 0; - update = zeros(1000, 1); - for b = 1:10 % Limit number of loops to avoid infinite loop case - for a = 1:numel(M) - M(a) = ~isequal(get(font_handles(a), 'FontName'), fonts_new{a}) || ~isequal(get(font_handles(a), 'FontSize'), fonts_size(a)); - if M(a) - set(font_handles(a), 'FontName', fonts_new{a}, 'FontSize', fonts_size(a)); - c = c + 1; - update(c) = a; + + % Retrieve padding, crop & font-swap values + crop_amounts = nan(1,4); % auto-crop all 4 sides by default + if isstruct(export_options) + try fontswap = export_options.fontswap; catch, fontswap = true; end + try font_space = export_options.font_space; catch, font_space = ''; end + font_space(2:end) = ''; + try bb_crop = export_options.crop; catch, bb_crop = 0; end + try crop_amounts = export_options.crop_amounts; catch, end + try bb_padding = export_options.bb_padding; catch, bb_padding = 0; end + try renderer = export_options.rendererStr; catch, renderer = 'opengl'; end % fix for issue #110 + if renderer(1)~='-', renderer = ['-' renderer]; end + else + if numel(export_options) > 2 % font-swapping + fontswap = export_options(3); + else + fontswap = true; + end + if numel(export_options) > 1 % cropping + bb_crop = export_options(2); + else + bb_crop = 0; % scalar value, so use default bb_crop value of 0 + end + if numel(export_options) > 0 % padding + bb_padding = export_options(1); + else + bb_padding = 0; + end + renderer = '-opengl'; + font_space = ''; + end + + % Construct the filename + if numel(name) < 5 || ~strcmpi(name(end-3:end), '.eps') + name = [name '.eps']; % Add the missing extension + end + + % Set paper size + old_pos_mode = get(fig, 'PaperPositionMode'); + old_orientation = get(fig, 'PaperOrientation'); + set(fig, 'PaperPositionMode', 'auto', 'PaperOrientation', 'portrait'); + + % Find all the used fonts in the figure + font_handles = findall(fig, '-property', 'FontName'); + fonts = get(font_handles, 'FontName'); + if isempty(fonts) + fonts = {}; + elseif ~iscell(fonts) + fonts = {fonts}; + end + + % Map supported font aliases onto the correct name + fontsl = lower(fonts); + for a = 1:numel(fonts) + f = fontsl{a}; + f(f==' ') = []; + switch f + case {'times', 'timesnewroman', 'times-roman'} + fontsl{a} = 'times-roman'; + case {'arial', 'helvetica'} + fontsl{a} = 'helvetica'; + case {'newcenturyschoolbook', 'newcenturyschlbk'} + fontsl{a} = 'newcenturyschlbk'; + otherwise + end + end + fontslu = unique(fontsl); + + % Determine the font swap table + if fontswap + matlab_fonts = {'Helvetica', 'Times-Roman', 'Palatino', 'Bookman', 'Helvetica-Narrow', 'Symbol', ... + 'AvantGarde', 'NewCenturySchlbk', 'Courier', 'ZapfChancery', 'ZapfDingbats'}; + matlab_fontsl = lower(matlab_fonts); + require_swap = find(~ismember(fontslu, matlab_fontsl)); + unused_fonts = find(~ismember(matlab_fontsl, fontslu)); + font_swap = cell(3, min(numel(require_swap), numel(unused_fonts))); + fonts_new = fonts; + for a = 1:size(font_swap, 2) + font_swap{1,a} = find(strcmp(fontslu{require_swap(a)}, fontsl)); + font_swap{2,a} = matlab_fonts{unused_fonts(a)}; + font_swap{3,a} = fonts{font_swap{1,a}(1)}; + fonts_new(font_swap{1,a}) = font_swap(2,a); + end + else + font_swap = []; + end + + % Swap the fonts + if ~isempty(font_swap) + fonts_size = get(font_handles, 'FontSize'); + if iscell(fonts_size) + fonts_size = cell2mat(fonts_size); + end + M = false(size(font_handles)); + + % Loop because some changes may not stick first time, due to listeners + c = 0; + update = zeros(1000, 1); + for b = 1:10 % Limit number of loops to avoid infinite loop case + for a = 1:numel(M) + M(a) = ~isequal(get(font_handles(a), 'FontName'), fonts_new{a}) || ~isequal(get(font_handles(a), 'FontSize'), fonts_size(a)); + if M(a) + set(font_handles(a), 'FontName', fonts_new{a}, 'FontSize', fonts_size(a)); + c = c + 1; + update(c) = a; + end + end + if ~any(M) + break; end end - if ~any(M) - break; + + % Compute the order to revert fonts later, without the need of a loop + [update, M] = unique(update(1:c)); + [dummy, M] = sort(M); %#ok + update = reshape(update(M), 1, []); + end + + % MATLAB bug fix - black and white text can come out inverted sometimes + % Find the white and black text + black_text_handles = findall(fig, 'Type', 'text', 'Color', [0 0 0]); + white_text_handles = findall(fig, 'Type', 'text', 'Color', [1 1 1]); + % Set the font colors slightly off their correct values + set(black_text_handles, 'Color', [0 0 0] + eps); + set(white_text_handles, 'Color', [1 1 1] - eps); + + % MATLAB bug fix - white lines can come out funny sometimes + % Find the white lines + white_line_handles = findall(fig, 'Type', 'line', 'Color', [1 1 1]); + % Set the line color slightly off white + set(white_line_handles, 'Color', [1 1 1] - 0.00001); + + % MATLAB bug fix (issue #211): dashed/dotted lines in 3D axes appear solid + % Note: this "may limit other functionality in plotting such as hidden line/surface removal" + % reference: Technical Support Case #02838114, https://mail.google.com/mail/u/0/#inbox/15fb7659f70e7bd8 + hAxes = findall(fig, 'Type', 'axes'); + if using_hg2 && ~isempty(hAxes) % issue #211 presumably happens only in HG2, not HG1 + try + % If there are any axes using SortMethod~='ChildOrder' + oldSortMethods = get(hAxes,{'SortMethod'}); % use {'SortMethod'} to ensure we get a cell array, even for single axes + if any(~strcmpi('ChildOrder',oldSortMethods)) % i.e., any oldSortMethods=='depth' + % Check if the axes look visually different onscreen when SortMethod='ChildOrder' + imgBefore = print2array(fig); + set(hAxes,'SortMethod','ChildOrder'); + imgAfter = print2array(fig); + if isequal(imgBefore, imgAfter) + % They look the same, so use SortMethod='ChildOrder' when generating the EPS + else + % They look different, so revert SortMethod and issue a warning message + warning('YMA:export_fig:issue211', ... + ['You seem to be using axes that have overlapping/hidden graphic elements. ' 10 ... + 'Setting axes.SortMethod=''ChildOrder'' may solve potential problems in EPS/PDF export. ' 10 ... + 'Additional info: https://github.com/altmany/export_fig/issues/211']) + set(hAxes,{'SortMethod'},oldSortMethods); + end + end + catch err + % ignore + a=err; %#ok % debug breakpoint end end - % Compute the order to revert fonts later, without the need of a loop - [update, M] = unique(update(1:c)); - [M, M] = sort(M); - update = reshape(update(M), 1, []); -end -% Set paper size -old_pos_mode = get(fig, 'PaperPositionMode'); -old_orientation = get(fig, 'PaperOrientation'); -set(fig, 'PaperPositionMode', 'auto', 'PaperOrientation', 'portrait'); -% MATLAB bug fix - black and white text can come out inverted sometimes -% Find the white and black text -white_text_handles = findobj(fig, 'Type', 'text'); -M = get(white_text_handles, 'Color'); -if iscell(M) - M = cell2mat(M); -end -M = sum(M, 2); -black_text_handles = white_text_handles(M == 0); -white_text_handles = white_text_handles(M == 3); -% Set the font colors slightly off their correct values -set(black_text_handles, 'Color', [0 0 0] + eps); -set(white_text_handles, 'Color', [1 1 1] - eps); -% MATLAB bug fix - white lines can come out funny sometimes -% Find the white lines -white_line_handles = findobj(fig, 'Type', 'line'); -M = get(white_line_handles, 'Color'); -if iscell(M) - M = cell2mat(M); -end -white_line_handles = white_line_handles(sum(M, 2) == 3); -% Set the line color slightly off white -set(white_line_handles, 'Color', [1 1 1] - 0.00001); -% Print to eps file -print(fig, options{:}, name); -% Reset the font and line colors -set(black_text_handles, 'Color', [0 0 0]); -set(white_text_handles, 'Color', [1 1 1]); -set(white_line_handles, 'Color', [1 1 1]); -% Reset paper size -set(fig, 'PaperPositionMode', old_pos_mode, 'PaperOrientation', old_orientation); -% Correct the fonts -if ~isempty(font_swap) - % Reset the font names in the figure - for a = update - set(font_handles(a), 'FontName', fonts{a}, 'FontSize', fonts_size(a)); + + % Workaround for issue #45: lines in image subplots are exported in invalid color + % In this case the -depsc driver solves the problem, but then all the other workarounds + % below (for all the other issues) will fail, so it's better to let the user decide by + % just issuing a warning and accepting the '-depsc' input parameter + epsLevel2 = ~any(strcmpi(options,'-depsc')); + if epsLevel2 + % Use -depsc2 (EPS color level-2) if -depsc (EPS color level-3) was not specifically requested + options{end+1} = '-depsc2'; + % Issue a warning if multiple images & lines were found in the figure, and HG1 with painters renderer is used + isPainters = any(strcmpi(options,'-painters')); + if isPainters && ~using_hg2 && numel(findall(fig,'Type','image'))>1 && ~isempty(findall(fig,'Type','line')) + warning('YMA:export_fig:issue45', ... + ['Multiple images & lines detected. In such cases, the lines might \n' ... + 'appear with an invalid color due to an internal MATLAB bug (fixed in R2014b). \n' ... + 'Possible workaround: add a ''-depsc'' or ''-opengl'' parameter to the export_fig command.']); + end end - % Replace the font names in the eps file - font_swap = font_swap(2:3,:); + + % Fix issue #83: use numeric handles in HG1 + if ~using_hg2(fig), fig = double(fig); end + + % Workaround for when transparency is lost through conversion fig>EPS>PDF (issue #108) + % Replace transparent patch RGB values with an ID value (rare chance that ID color is being used already) + if using_hg2 + origAlphaColors = eps_maintainAlpha(fig); + end + + % Print to eps file + print(fig, options{:}, name); + + % Restore the original axes SortMethods (if updated) + try set(hAxes,{'SortMethod'},oldSortMethods); catch, end + + % Do post-processing on the eps file try - swap_fonts(name, font_swap{:}); + % Read the EPS file into memory + fstrm = read_write_entire_textfile(name); catch - warning('swap_fonts() failed. This is usually because the figure contains a large number of patch objects. Consider exporting to a bitmap format in this case.'); + fstrm = ''; + end + + % Restore colors for transparent patches/lines and apply the + % setopacityalpha setting in the EPS file (issue #108) + if using_hg2 + [~,fstrm,foundFlags] = eps_maintainAlpha(fig, fstrm, origAlphaColors); + + % If some of the transparencies were not found in the EPS file, then rerun the + % export with only the found transparencies modified (backward compatibility) + if ~isempty(fstrm) && ~all(foundFlags) + foundIdx = find(foundFlags); + for objIdx = 1 : sum(foundFlags) + colorsIdx = foundIdx(objIdx); + colorsData = origAlphaColors{colorsIdx}; + hObj = colorsData{1}; + propName = colorsData{2}; + newColor = colorsData{4}; + hObj.(propName).ColorData = newColor; + end + delete(name); + print(fig, options{:}, name); + fstrm = read_write_entire_textfile(name); + [~,fstrm] = eps_maintainAlpha(fig, fstrm, origAlphaColors(foundFlags)); + end + end + + % Fix for Matlab R2014b bug (issue #31): LineWidths<0.75 are not set in the EPS (default line width is used) + try + if ~isempty(fstrm) && using_hg2(fig) + % Convert miter joins to line joins + %fstrm = regexprep(fstrm, '\n10.0 ML\n', '\n1 LJ\n'); + % This is faster (the original regexprep could take many seconds when the axes contains many lines): + fstrm = strrep(fstrm, sprintf('\n10.0 ML\n'), sprintf('\n1 LJ\n')); + + % In HG2, grid lines and axes Ruler Axles have a default LineWidth of 0.5 => replace en-bulk (assume that 1.0 LineWidth = 1.333 LW) + % hAxes=gca; hAxes.YGridHandle.LineWidth, hAxes.YRuler.Axle.LineWidth + %fstrm = regexprep(fstrm, '(GC\n2 setlinecap\n1 LJ)\nN', '$1\n0.667 LW\nN'); + % This is faster: + fstrm = strrep(fstrm, sprintf('GC\n2 setlinecap\n1 LJ\nN'), sprintf('GC\n2 setlinecap\n1 LJ\n0.667 LW\nN')); + + % This is more accurate but *MUCH* slower (issue #52) + %{ + % Modify all thin lines in the figure to have 10x LineWidths + hLines = findall(fig,'Type','line'); + hThinLines = []; + for lineIdx = 1 : numel(hLines) + thisLine = hLines(lineIdx); + if thisLine.LineWidth < 0.75 && strcmpi(thisLine.Visible,'on') + hThinLines(end+1) = thisLine; %#ok + thisLine.LineWidth = thisLine.LineWidth * 10; + end + end + + % If any thin lines were found + if ~isempty(hThinLines) + % Prepare an EPS with large-enough line widths + print(fig, options{:}, name); + % Restore the original LineWidths in the figure + for lineIdx = 1 : numel(hThinLines) + thisLine = handle(hThinLines(lineIdx)); + thisLine.LineWidth = thisLine.LineWidth / 10; + end + + % Compare the original and the new EPS files and correct the original stream's LineWidths + fstrm_new = read_write_entire_textfile(name); + idx = 500; % skip heading with its possibly-different timestamp + markerStr = sprintf('10.0 ML\nN'); + markerLen = length(markerStr); + while ~isempty(idx) && idx < length(fstrm) + lastIdx = min(length(fstrm), length(fstrm_new)); + delta = fstrm(idx+1:lastIdx) - fstrm_new(idx+1:lastIdx); + idx = idx + find(delta,1); + if ~isempty(idx) && ... + isequal(fstrm(idx-markerLen+1:idx), markerStr) && ... + ~isempty(regexp(fstrm_new(idx-markerLen+1:idx+12),'10.0 ML\n[\d\.]+ LW\nN')) %#ok + value = str2double(regexprep(fstrm_new(idx:idx+12),' .*','')); + if isnan(value), break; end % something's wrong... - bail out + newStr = sprintf('%0.3f LW\n',value/10); + fstrm = [fstrm(1:idx-1) newStr fstrm(idx:end)]; + idx = idx + 12; + else + break; + end + end + end + %} + + % This is much faster although less accurate: fix all non-gray lines to have a LineWidth of 0.75 (=1 LW) + % Note: This will give incorrect LineWidth of 075 for lines having LineWidth<0.75, as well as for non-gray grid-lines (if present) + % However, in practice these edge-cases are very rare indeed, and the difference in LineWidth should not be noticeable + %fstrm = regexprep(fstrm, '([CR]C\n2 setlinecap\n1 LJ)\nN', '$1\n1 LW\nN'); + % This is faster (the original regexprep could take many seconds when the axes contains many lines): + fstrm = strrep(fstrm, sprintf('\n2 setlinecap\n1 LJ\nN'), sprintf('\n2 setlinecap\n1 LJ\n1 LW\nN')); + end + catch err + fprintf(2, 'Error fixing LineWidths in EPS file: %s\n at %s:%d\n', err.message, err.stack(1).file, err.stack(1).line); + end + + % Reset the font and line colors + try + set(black_text_handles, 'Color', [0 0 0]); + set(white_text_handles, 'Color', [1 1 1]); + catch + % Fix issue #159: redo findall() '*text_handles' + black_text_handles = findall(fig, 'Type', 'text', 'Color', [0 0 0]+eps); + white_text_handles = findall(fig, 'Type', 'text', 'Color', [1 1 1]-eps); + set(black_text_handles, 'Color', [0 0 0]); + set(white_text_handles, 'Color', [1 1 1]); + end + set(white_line_handles, 'Color', [1 1 1]); + + % Reset paper size + set(fig, 'PaperPositionMode', old_pos_mode, 'PaperOrientation', old_orientation); + + % Reset the font names in the figure + if ~isempty(font_swap) + for a = update + set(font_handles(a), 'FontName', fonts{a}, 'FontSize', fonts_size(a)); + end + end + + % Bail out if EPS post-processing is not possible + if isempty(fstrm) + warning('Loading EPS file failed, so unable to perform post-processing. This is usually because the figure contains a large number of patch objects. Consider exporting to a bitmap format in this case.'); return end -end -% Fix the line styles -try - fix_lines(name); -catch - warning('fix_lines() failed. This is usually because the figure contains a large number of patch objects. Consider exporting to a bitmap format in this case.'); -end -return -function swap_fonts(fname, varargin) -% Read in the file -fh = fopen(fname, 'r'); -if fh == -1 - error('File %s not found.', fname); -end -try - fstrm = fread(fh, '*char')'; -catch ex - fclose(fh); - rethrow(ex); -end -fclose(fh); + % Replace the font names + if ~isempty(font_swap) + for a = 1:size(font_swap, 2) + fontName = font_swap{3,a}; + %fontName = fontName(~isspace(font_swap{3,a})); + if length(fontName) > 29 + warning('YMA:export_fig:font_name','Font name ''%s'' is longer than 29 characters. This might cause problems in some EPS/PDF readers. Consider using a different font.',fontName); + end + if isempty(font_space) + fontName(fontName==' ') = ''; + else + fontName(fontName==' ') = char(font_space); + end + %fstrm = regexprep(fstrm, [font_swap{1,a} '-?[a-zA-Z]*\>'], fontName); + fstrm = regexprep(fstrm, font_swap{2,a}, fontName); + end + end -% Replace the font names -for a = 1:2:numel(varargin) - fstrm = regexprep(fstrm, [varargin{a} '-?[a-zA-Z]*\>'], varargin{a+1}(~isspace(varargin{a+1}))); -end + % Move the bounding box to the top of the file (HG2 only), or fix the line styles (HG1 only) + if using_hg2(fig) + % Move the bounding box to the top of the file (HG2 only) + [s, e] = regexp(fstrm, '%%BoundingBox: [^%]*%%'); + if numel(s) == 2 + fstrm = fstrm([1:s(1)-1 s(2):e(2)-2 e(1)-1:s(2)-1 e(2)-1:end]); + end + else + % Fix the line styles (HG1 only) + fstrm = fix_lines(fstrm); + end + + % Apply the bounding box padding & cropping, replacing Matlab's print()'s bounding box + if bb_crop + % Calculate a new bounding box based on a bitmap print using crop_border.m + % 1. Determine the Matlab BoundingBox and PageBoundingBox + [s,e] = regexp(fstrm, '%%BoundingBox: [^%]*%%'); % location BB in eps file + if numel(s)==2, s=s(2); e=e(2); end + aa = fstrm(s+15:e-3); % dimensions bb - STEP1 + bb_matlab = cell2mat(textscan(aa,'%f32%f32%f32%f32')); % dimensions bb - STEP2 + + [s,e] = regexp(fstrm, '%%PageBoundingBox: [^%]*%%'); % location bb in eps file + if numel(s)==2, s=s(2); e=e(2); end + aa = fstrm(s+19:e-3); % dimensions bb - STEP1 + pagebb_matlab = cell2mat(textscan(aa,'%f32%f32%f32%f32')); % dimensions bb - STEP2 + + % 1b. Fix issue #239: black title meshes with temporary black background figure bgcolor, causing bad cropping + hTitles = []; + if isequal(get(fig,'Color'),'none') + hAxes = findall(fig,'type','axes'); + for idx = 1 : numel(hAxes) + hAx = hAxes(idx); + try + hTitle = hAx.Title; + oldColor = hTitle.Color; + if all(oldColor < 5*eps) || (ischar(oldColor) && lower(oldColor(1))=='k') + hTitles(end+1) = hTitle; %#ok + hTitle.Color = [0,0,.01]; + end + catch + end + end + end + + % 2. Create a bitmap image and use crop_borders to create the relative + % bb with respect to the PageBoundingBox + [A, bcol] = print2array(fig, 1, renderer); + [aa, aa, aa, bb_rel] = crop_borders(A, bcol, bb_padding, crop_amounts); %#ok + + try set(hTitles,'Color','k'); catch, end + + % 3. Calculate the new Bounding Box + pagew = pagebb_matlab(3)-pagebb_matlab(1); + pageh = pagebb_matlab(4)-pagebb_matlab(2); + %bb_new = [pagebb_matlab(1)+pagew*bb_rel(1) pagebb_matlab(2)+pageh*bb_rel(2) ... + % pagebb_matlab(1)+pagew*bb_rel(3) pagebb_matlab(2)+pageh*bb_rel(4)]; + bb_new = pagebb_matlab([1,2,1,2]) + [pagew,pageh,pagew,pageh].*bb_rel; % clearer + bb_offset = (bb_new-bb_matlab) + [-2,-2,2,2]; % 2px margin so that cropping is not TOO tight (issue #195) -% Write out the updated file -fh = fopen(fname, 'w'); -if fh == -1 - error('Unable to open %s for writing.', fname2); + % Apply the bounding box padding + if bb_padding + if abs(bb_padding)<1 + bb_padding = round((mean([bb_new(3)-bb_new(1) bb_new(4)-bb_new(2)])*bb_padding)/0.5)*0.5; % ADJUST BB_PADDING + end + add_padding = @(n1, n2, n3, n4) sprintf(' %.0f', str2double({n1, n2, n3, n4}) + bb_offset + bb_padding*[-1,-1,1,1]); %#ok + else + add_padding = @(n1, n2, n3, n4) sprintf(' %.0f', str2double({n1, n2, n3, n4}) + bb_offset); %#ok % fix small but noticeable bounding box shift + end + fstrm = regexprep(fstrm, '%%BoundingBox:[ ]+([-]?\d+)[ ]+([-]?\d+)[ ]+([-]?\d+)[ ]+([-]?\d+)', '%%BoundingBox:${add_padding($1, $2, $3, $4)}'); + end + + % Fix issue #44: white artifact lines appearing in patch exports + % Note: the problem is due to the fact that Matlab's print() function exports patches + % as a combination of filled triangles, and a white line appears where the triangles touch + % In the workaround below, we will modify such dual-triangles into a filled rectangle. + % We are careful to only modify regexps that exactly match specific patterns - it's better to not + % correct some white-line artifacts than to change the geometry of a patch, or to corrupt the EPS. + % e.g.: '0 -450 937 0 0 450 3 MP PP 937 0 0 -450 0 450 3 MP PP' => '0 -450 937 0 0 450 0 0 4 MP' + fstrm = regexprep(fstrm, '\n([-\d.]+ [-\d.]+) ([-\d.]+ [-\d.]+) ([-\d.]+ [-\d.]+) 3 MP\nPP\n\2 \1 \3 3 MP\nPP\n','\n$1 $2 $3 0 0 4 MP\nPP\n'); + fstrm = regexprep(fstrm, '\n([-\d.]+ [-\d.]+) ([-\d.]+ [-\d.]+) ([-\d.]+ [-\d.]+) 3 MP\nPP\n\2 \3 \1 3 MP\nPP\n','\n$1 $2 $3 0 0 4 MP\nPP\n'); + fstrm = regexprep(fstrm, '\n([-\d.]+ [-\d.]+) ([-\d.]+ [-\d.]+) ([-\d.]+ [-\d.]+) 3 MP\nPP\n\3 \1 \2 3 MP\nPP\n','\n$1 $2 $3 0 0 4 MP\nPP\n'); + fstrm = regexprep(fstrm, '\n([-\d.]+ [-\d.]+) ([-\d.]+ [-\d.]+) ([-\d.]+ [-\d.]+) 3 MP\nPP\n\3 \2 \1 3 MP\nPP\n','\n$1 $2 $3 0 0 4 MP\nPP\n'); + + % Write out the fixed eps file + read_write_entire_textfile(name, fstrm); end -try - fwrite(fh, fstrm, 'char*1'); -catch ex - fclose(fh); - rethrow(ex); + +function [StoredColors, fstrm, foundFlags] = eps_maintainAlpha(fig, fstrm, StoredColors) + if nargin == 1 % in: convert transparency in Matlab figure into unique RGB colors + hObjs = findall(fig); %findobj(fig,'Type','Area'); + StoredColors = {}; + propNames = {'Face','Edge'}; + for objIdx = 1:length(hObjs) + hObj = hObjs(objIdx); + for propIdx = 1 : numel(propNames) + try + propName = propNames{propIdx}; + if strcmp(hObj.(propName).ColorType, 'truecoloralpha') + nColors = length(StoredColors); + oldColor = hObj.(propName).ColorData; + newColor = uint8([101; 102+floor(nColors/255); mod(nColors,255); 255]); + StoredColors{end+1} = {hObj, propName, oldColor, newColor}; %#ok + hObj.(propName).ColorData = newColor; + end + catch + % Never mind - ignore (either doesn't have the property or cannot change it) + end + end + end + else % restore transparency in Matlab figure by converting back from the unique RGBs + %Find the transparent patches + wasError = false; + nColors = length(StoredColors); + foundFlags = false(1,nColors); + for objIdx = 1 : nColors + colorsData = StoredColors{objIdx}; + hObj = colorsData{1}; + propName = colorsData{2}; + origColor = colorsData{3}; + newColor = colorsData{4}; + try + %Restore the EPS files patch color + colorID = num2str(round(double(newColor(1:3)') /255,3),'%.3g %.3g %.3g'); %ID for searching + origRGB = num2str(round(double(origColor(1:3)')/255,3),'%.3g %.3g %.3g'); %Replace with original color + origAlpha = num2str(round(double(origColor(end)) /255,3),'%.3g'); %Convert alpha value for EPS + + %Find and replace the RGBA values within the EPS text fstrm + if strcmpi(propName,'Face') + oldStr = sprintf(['\n' colorID ' RC\n']); % ...N\n (removed to fix issue #225) + newStr = sprintf(['\n' origRGB ' RC\n' origAlpha ' .setopacityalpha true\n']); % ...N\n + else %'Edge' + oldStr = sprintf(['\n' colorID ' RC\n']); % ...1 LJ\n (removed to fix issue #225) + newStr = sprintf(['\n' origRGB ' RC\n' origAlpha ' .setopacityalpha true\n']); + end + foundFlags(objIdx) = ~isempty(strfind(fstrm, oldStr)); %#ok + fstrm = strrep(fstrm, oldStr, newStr); + + %Restore the figure object's original color + hObj.(propName).ColorData = origColor; + catch err + % something is wrong - cannot restore transparent color... + if ~wasError + fprintf(2, 'Error maintaining transparency in EPS file: %s\n at %s:%d\n', err.message, err.stack(1).file, err.stack(1).line); + wasError = true; + end + end + end + end end -fclose(fh); -return diff --git a/tools/plotting/export_fig/read_write_entire_textfile.m b/tools/plotting/export_fig/read_write_entire_textfile.m new file mode 100644 index 0000000..9fabb22 --- /dev/null +++ b/tools/plotting/export_fig/read_write_entire_textfile.m @@ -0,0 +1,37 @@ +%READ_WRITE_ENTIRE_TEXTFILE Read or write a whole text file to/from memory +% +% Read or write an entire text file to/from memory, without leaving the +% file open if an error occurs. +% +% Reading: +% fstrm = read_write_entire_textfile(fname) +% Writing: +% read_write_entire_textfile(fname, fstrm) +% +%IN: +% fname - Pathname of text file to be read in. +% fstrm - String to be written to the file, including carriage returns. +% +%OUT: +% fstrm - String read from the file. If an fstrm input is given the +% output is the same as that input. + +function fstrm = read_write_entire_textfile(fname, fstrm) +modes = {'rt', 'wt'}; +writing = nargin > 1; +fh = fopen(fname, modes{1+writing}); +if fh == -1 + error('Unable to open file %s.', fname); +end +try + if writing + fwrite(fh, fstrm, 'char*1'); + else + fstrm = fread(fh, '*char')'; + end +catch ex + fclose(fh); + rethrow(ex); +end +fclose(fh); +end diff --git a/tools/plotting/export_fig/user_string.m b/tools/plotting/export_fig/user_string.m old mode 100755 new mode 100644 index 655f862..48d1131 --- a/tools/plotting/export_fig/user_string.m +++ b/tools/plotting/export_fig/user_string.m @@ -1,24 +1,27 @@ +function string = user_string(string_name, string) %USER_STRING Get/set a user specific string % % Examples: -% string = user_string(string_name) -% saved = user_string(string_name, new_string) +% string = user_string(string_name) +% isSaved = user_string(string_name, new_string) % % Function to get and set a string in a system or user specific file. This % enables, for example, system specific paths to binaries to be saved. % +% The specified string will be saved in a file named .txt, +% either in a subfolder named .ignore under this file's folder, or in the +% user's prefdir folder (in case this file's folder is non-writable). +% % IN: -% string_name - String containing the name of the string required. The -% string is extracted from a file called (string_name).txt, -% stored in the same directory as user_string.m. -% new_string - The new string to be saved under the name given by -% string_name. +% string_name - String containing the name of the string required, which +% sets the filename storing the string: .txt +% new_string - The new string to be saved in the .txt file % % OUT: -% string - The currently saved string. Default: ''. -% saved - Boolean indicating whether the save was succesful +% string - The currently saved string. Default: '' +% isSaved - Boolean indicating whether the save was succesful -% Copyright (C) Oliver Woodford 2011-2013 +% Copyright (C) Oliver Woodford 2011-2014, Yair Altman 2015- % This method of saving paths avoids changing .m files which might be in a % version control system. Instead it saves the user dependent paths in @@ -27,61 +30,82 @@ % approach. % 10/01/2013 - Access files in text, not binary mode, as latter can cause -% errors. Thanks to Christian for pointing this out. +% errors. Thanks to Christian for pointing this out. +% 29/05/2015 - Save file in prefdir if current folder is non-writable (issue #74) +% 09/01/2018 - Fix issue #232: if the string looks like a file/folder path, ensure it actually exists -function string = user_string(string_name, string) -if ~ischar(string_name) - error('string_name must be a string.'); -end -% Create the full filename -string_name = fullfile(fileparts(mfilename('fullpath')), '.ignore', [string_name '.txt']); -if nargin > 1 - % Set string - if ~ischar(string) - error('new_string must be a string.'); + if ~ischar(string_name) + error('string_name must be a string.'); end - % Make sure the save directory exists - dname = fileparts(string_name); - if ~exist(dname, 'dir') - % Create the directory - try - if ~mkdir(dname) + % Create the full filename + fname = [string_name '.txt']; + dname = fullfile(fileparts(mfilename('fullpath')), '.ignore'); + file_name = fullfile(dname, fname); + if nargin > 1 + % Set string + if ~ischar(string) + error('new_string must be a string.'); + end + % Make sure the save directory exists + %dname = fileparts(file_name); + if ~exist(dname, 'dir') + % Create the directory + try + if ~mkdir(dname) + string = false; + return + end + catch string = false; return end + % Make it hidden + try + fileattrib(dname, '+h'); + catch + end + end + % Write the file + fid = fopen(file_name, 'wt'); + if fid == -1 + % file cannot be created/updated - use prefdir if file does not already exist + % (if file exists but is simply not writable, don't create a duplicate in prefdir) + if ~exist(file_name,'file') + file_name = fullfile(prefdir, fname); + fid = fopen(file_name, 'wt'); + end + if fid == -1 + string = false; + return; + end + end + try + fprintf(fid, '%s', string); catch + fclose(fid); string = false; return end - % Make it hidden - try - fileattrib(dname, '+h'); - catch + fclose(fid); + string = true; + else + % Get string + fid = fopen(file_name, 'rt'); + if fid == -1 + % file cannot be read, try to read the file in prefdir + file_name = fullfile(prefdir, fname); + fid = fopen(file_name, 'rt'); + if fid == -1 + string = ''; + return + end end - end - % Write the file - fid = fopen(string_name, 'wt'); - if fid == -1 - string = false; - return - end - try - fprintf(fid, '%s', string); - catch + string = fgetl(fid); fclose(fid); - string = false; - return - end - fclose(fid); - string = true; -else - % Get string - fid = fopen(string_name, 'rt'); - if fid == -1 - string = ''; - return + + % Fix issue #232: if the string looks like a file/folder path, ensure it actually exists + if ~isempty(string) && any(string=='\' | string=='/') && ~exist(string) %#ok + string = ''; + end end - string = fgetl(fid); - fclose(fid); end -return \ No newline at end of file diff --git a/tools/plotting/export_fig/using_hg2.m b/tools/plotting/export_fig/using_hg2.m new file mode 100644 index 0000000..ba72228 --- /dev/null +++ b/tools/plotting/export_fig/using_hg2.m @@ -0,0 +1,36 @@ +%USING_HG2 Determine if the HG2 graphics engine is used +% +% tf = using_hg2(fig) +% +%IN: +% fig - handle to the figure in question. +% +%OUT: +% tf - boolean indicating whether the HG2 graphics engine is being used +% (true) or not (false). + +% 19/06/2015 - Suppress warning in R2015b; cache result for improved performance +% 06/06/2016 - Fixed issue #156 (bad return value in R2016b) + +function tf = using_hg2(fig) + persistent tf_cached + if isempty(tf_cached) + try + if nargin < 1, fig = figure('visible','off'); end + oldWarn = warning('off','MATLAB:graphicsversion:GraphicsVersionRemoval'); + try + % This generates a [supressed] warning in R2015b: + tf = ~graphicsversion(fig, 'handlegraphics'); + catch + tf = ~verLessThan('matlab','8.4'); % =R2014b + end + warning(oldWarn); + catch + tf = false; + end + if nargin < 1, delete(fig); end + tf_cached = tf; + else + tf = tf_cached; + end +end diff --git a/tools/plotting/subplot_tight.m b/tools/plotting/subplot_tight.m new file mode 100644 index 0000000..049d781 --- /dev/null +++ b/tools/plotting/subplot_tight.m @@ -0,0 +1,99 @@ +function vargout=subplot_tight(m, n, p, margins, varargin) +%% subplot_tight +% A subplot function substitude with margins user tunabble parameter. +% +%% Syntax +% h=subplot_tight(m, n, p); +% h=subplot_tight(m, n, p, margins); +% h=subplot_tight(m, n, p, margins, subplotArgs...); +% +%% Description +% Our goal is to grant the user the ability to define the margins between neighbouring +% subplots. Unfotrtunately Matlab subplot function lacks this functionality, and the +% margins between subplots can reach 40% of figure area, which is pretty lavish. While at +% the begining the function was implememnted as wrapper function for Matlab function +% subplot, it was modified due to axes del;etion resulting from what Matlab subplot +% detected as overlapping. Therefore, the current implmenetation makes no use of Matlab +% subplot function, using axes instead. This can be problematic, as axis and subplot +% parameters are quie different. Set isWrapper to "True" to return to wrapper mode, which +% fully supports subplot format. +% +%% Input arguments (defaults exist): +% margins- two elements vector [vertical,horizontal] defining the margins between +% neighbouring axes. Default value is 0.04 +% +%% Output arguments +% same as subplot- none, or axes handle according to function call. +% +%% Issues & Comments +% - Note that if additional elements are used in order to be passed to subplot, margins +% parameter must be defined. For default margins value use empty element- []. +% - +% +%% Example +% close all; +% img=imread('peppers.png'); +% figSubplotH=figure('Name', 'subplot'); +% figSubplotTightH=figure('Name', 'subplot_tight'); +% nElems=17; +% subplotRows=ceil(sqrt(nElems)-1); +% subplotRows=max(1, subplotRows); +% subplotCols=ceil(nElems/subplotRows); +% for iElem=1:nElems +% figure(figSubplotH); +% subplot(subplotRows, subplotCols, iElem); +% imshow(img); +% figure(figSubplotTightH); +% subplot_tight(subplotRows, subplotCols, iElem, [0.0001]); +% imshow(img); +% end +% +%% See also +% - subplot +% +%% Revision history +% First version: Nikolay S. 2011-03-29. +% Last update: Nikolay S. 2012-05-24. +% +% *List of Changes:* +% 2012-05-24 +% Non wrapping mode (based on axes command) added, to deal with an issue of disappearing +% subplots occuring with massive axes. + +%% Default params +isWrapper=false; +if (nargin<4) || isempty(margins) + margins=[0.04,0.04]; % default margins value- 4% of figure +end +if length(margins)==1 + margins(2)=margins; +end + +%note n and m are switched as Matlab indexing is column-wise, while subplot indexing is row-wise :( +[subplot_col,subplot_row]=ind2sub([n,m],p); + + +height=(1-(m+1)*margins(1))/m; % single subplot height +width=(1-(n+1)*margins(2))/n; % single subplot width + +% note subplot suppors vector p inputs- so a merged subplot of higher dimentions will be created +subplot_cols=1+max(subplot_col)-min(subplot_col); % number of column elements in merged subplot +subplot_rows=1+max(subplot_row)-min(subplot_row); % number of row elements in merged subplot + +merged_height=subplot_rows*( height+margins(1) )- margins(1); % merged subplot height +merged_width= subplot_cols*( width +margins(2) )- margins(2); % merged subplot width + +merged_bottom=(m-max(subplot_row))*(height+margins(1)) +margins(1); % merged subplot bottom position +merged_left=min(subplot_col)*(width+margins(2))-width; % merged subplot left position +pos=[merged_left, merged_bottom, merged_width, merged_height]; + + +if isWrapper + h=subplot(m, n, p, varargin{:}, 'Units', 'Normalized', 'Position', pos); +else + h=axes('Position', pos, varargin{:}); +end + +if nargout==1 + vargout=h; +end \ No newline at end of file From 88b1860f88f3d4ef7b8d2d190450304d1d7f5fc2 Mon Sep 17 00:00:00 2001 From: sebobosse Date: Fri, 4 Jan 2019 13:37:18 +0100 Subject: [PATCH 51/62] minor changes (allow for different folder setups and reduced number of subjects) --- Examples/test_spatial_filters_2sources.m | 27 ++++++++++++++++++------ 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/Examples/test_spatial_filters_2sources.m b/Examples/test_spatial_filters_2sources.m index a88990d..e362a0b 100644 --- a/Examples/test_spatial_filters_2sources.m +++ b/Examples/test_spatial_filters_2sources.m @@ -2,11 +2,24 @@ clear;clc mrCFolder = fileparts(fileparts(mfilename('fullpath')));%'/Users/kohler/code/git'; addpath(genpath(mrCFolder)); -addpath('../../../BrewerMap/') -%% -DestPath = 'ExampleData2'; -AnatomyPath = fullfile(DestPath,'anatomy'); -ProjectPath = fullfile(DestPath,'FwdProject'); +if true % SBs setup + addpath('../tools/BrewerMap/') + %% + DataPath = '/export/data/'; + + DestPath = fullfile(DataPath,'eeg_simulation'); + AnatomyPath = fullfile(DestPath,'anatomy'); + + ProjectPath = fullfile(DestPath,'FwdProject2'); +else + addpath('../../../BrewerMap/') + + %% + DestPath = 'ExampleData2'; + + AnatomyPath = fullfile(DestPath,'anatomy'); + ProjectPath = fullfile(DestPath,'FwdProject'); +end % Pre-select ROIs [RoiList,subIDs] = mrC.Simulate.GetRoiClass(ProjectPath,AnatomyPath);% 13 subjects with Wang atlab @@ -56,8 +69,8 @@ n_comps = 3 ; thisFundFreq = FundFreq(fund_freq_idx) ; -subs = num2cell(1:10) ; %%%%%% SUBJECTS TO SELECT -subNames = cellfun(@num2str,subs(1:10),'uni',false); +subs = num2cell(1:min(length(subIDs),10)) ; %%%%%% SUBJECTS TO SELECT +subNames = cellfun(@num2str,subs(1:min(length(subIDs),10)),'uni',false); EEGData_noise = cellfun(@(x) x(:,:,1:200),EEGData_noise,'uni',false); % reduce data size EEGAxx_noise = cellfun(@(x) x.SelectTrials(1:200),EEGAxx_noise,'uni',false); From 9763f70c81810e9012e03740791beb79d50f41cf Mon Sep 17 00:00:00 2001 From: sebobosse Date: Fri, 4 Jan 2019 13:39:09 +0100 Subject: [PATCH 52/62] minor change to improve readability --- Examples/test_spatial_filters_2sources.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Examples/test_spatial_filters_2sources.m b/Examples/test_spatial_filters_2sources.m index e362a0b..8c91b5e 100644 --- a/Examples/test_spatial_filters_2sources.m +++ b/Examples/test_spatial_filters_2sources.m @@ -91,7 +91,7 @@ % define SNR in a narrow frequency bands on first forth harmonics lambda = Lambda_list(nLambda_idx); disp(['Generating EEG by adding signal and noise: SNR = ' num2str(lambda)]); - for subj_idx = 1:length(EEGData_signal) + for subj_idx = 1:length(subIDs) [Sig] = mean(EEGAxx_signal{subj_idx}.Amp(F1+1,:).^2,2); Noi = mean(mean(EEGAxx_noise{subj_idx}.Amp(F1:F1+1,:).^2,2)); EEGData{subj_idx} = sqrt(Noi/Sig)*sqrt(lambda/(1+lambda))*EEGData_signal{subj_idx} + sqrt(1/(1+lambda)) * EEGData_noise{subj_idx} ; From 78a4b886ccb861c090ff666a52df671adf4448be Mon Sep 17 00:00:00 2001 From: sebobosse Date: Fri, 4 Jan 2019 17:23:48 +0100 Subject: [PATCH 53/62] corrected narrow-band normalization --- Examples/test_spatial_filters_2sources.m | 25 ++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/Examples/test_spatial_filters_2sources.m b/Examples/test_spatial_filters_2sources.m index 8c91b5e..bb826c7 100644 --- a/Examples/test_spatial_filters_2sources.m +++ b/Examples/test_spatial_filters_2sources.m @@ -85,16 +85,29 @@ end end -for nLambda_idx = 1:numel(Lambda_list) +% narrow-band normalization +% harmonics considered for normalization +snr_harmonics = [1,2,3,4] ; - - % define SNR in a narrow frequency bands on first forth harmonics +f = [0:length(EEGData_signal{1})-1] *SF/length(EEGData_signal{1}) ; +[~,snr_freq_idxs]=intersect(f,snr_harmonics*thisFundFreq) ; + +for subj_idx = 1:length(subIDs) + %% + spec_noise = fft(EEGData_noise{subj_idx},[],1); + spec_signal = fft(EEGData_signal{subj_idx},[],1); + power_noise = mean(mean(abs(spec_noise(snr_freq_idxs,:,:)).^2)) ; % mean noise power per trial + power_signal= mean(mean(abs(spec_signal(snr_freq_idxs,:,:)).^2)) ; + EEGData_noise{subj_idx} = EEGData_noise{subj_idx}./sqrt(power_noise); + EEGData_signal{subj_idx} = EEGData_signal{subj_idx}./sqrt(power_signal); +end + + +for nLambda_idx = 1:numel(Lambda_list) lambda = Lambda_list(nLambda_idx); disp(['Generating EEG by adding signal and noise: SNR = ' num2str(lambda)]); for subj_idx = 1:length(subIDs) - [Sig] = mean(EEGAxx_signal{subj_idx}.Amp(F1+1,:).^2,2); - Noi = mean(mean(EEGAxx_noise{subj_idx}.Amp(F1:F1+1,:).^2,2)); - EEGData{subj_idx} = sqrt(Noi/Sig)*sqrt(lambda/(1+lambda))*EEGData_signal{subj_idx} + sqrt(1/(1+lambda)) * EEGData_noise{subj_idx} ; + EEGData{subj_idx} = sqrt(lambda/(1+lambda))*EEGData_signal{subj_idx} + sqrt(1/(1+lambda)) * EEGData_noise{subj_idx} ; EEGAxx{subj_idx} = mrC.Simulate.CreateAxx(EEGData{subj_idx},opt) ; end From 1407a10ad408644a31a00c966240218f38bc0e15 Mon Sep 17 00:00:00 2001 From: sebobosse Date: Sun, 6 Jan 2019 22:08:59 +0100 Subject: [PATCH 54/62] minor fixes --- Examples/test_spatial_filters_2sources.m | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Examples/test_spatial_filters_2sources.m b/Examples/test_spatial_filters_2sources.m index bb826c7..1da12ac 100644 --- a/Examples/test_spatial_filters_2sources.m +++ b/Examples/test_spatial_filters_2sources.m @@ -172,7 +172,7 @@ thisDecompAxx=theseDecompAxxs{1}; end for i = 1:size(thisA,2) - if source_pattern(:,i)'*thisA(:,i)<0 + if source_pattern(:,1)'*thisA(:,i)<0 thisA(:,i) = thisA(:,i)*-1 ; end end @@ -303,13 +303,13 @@ hold on end end -%[~, hobj, ~, ~] = legend(decomp_methods(1:2)); +[~, hobj, ~, ~] = legend(decomp_methods(1:2)); hl = findobj(hobj,'type','line'); set(hl,'LineWidth',1.5); ht = findobj(hobj,'type','text'); set(ht,'FontSize',12); set(gca,'xtick',10*log10(Lambda_list),'xticklabel',arrayfun(@num2str,round(log10(Lambda_list)*10),'uni',false)); -xlim(10*[0-.1 1.1]); +xlim(10*log10([min(Lambda_list),max(Lambda_list)])); xlabel('SNR (dB)') ylabel('Error Angle') @@ -335,7 +335,7 @@ xlabel('SNR (dB)') ylabel('Output SNR (dB)') set(gca,'xtick',10*log10(Lambda_list),'xticklabel',arrayfun(@num2str,round(log10(Lambda_list)*10),'uni',false)); -xlim(10*[0-.1 1.1]); +xlim(10*log10([min(Lambda_list),max(Lambda_list)])); set(gca,'fontsize',FS) % plot residual @@ -359,7 +359,7 @@ ylabel('Residuals') set(gca,'fontsize',FS) set(gca,'xtick',10*log10(Lambda_list),'xticklabel',arrayfun(@num2str,round(log10(Lambda_list)*10),'uni',false)); -xlim(10*[0-.1 1.1]); +xlim(10*log10([min(Lambda_list),max(Lambda_list)])); set(FIG2,'Unit','Inch','position',[5, 5, 18, 5],'color','w'); export_fig(FIG2,['ErrorPlots_Averaged'],'-pdf'); From eadcd5cdf90a14cd2cf61a2369c18132989e4c3d Mon Sep 17 00:00:00 2001 From: sebobosse Date: Sun, 6 Jan 2019 22:09:36 +0100 Subject: [PATCH 55/62] changed range of input snr --- Examples/test_spatial_filters_2sources.m | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Examples/test_spatial_filters_2sources.m b/Examples/test_spatial_filters_2sources.m index 1da12ac..f0e65f5 100644 --- a/Examples/test_spatial_filters_2sources.m +++ b/Examples/test_spatial_filters_2sources.m @@ -60,7 +60,9 @@ % narrowbanded % SNR parameters F1 = EEGAxx_signal{1,1}.i1F1; -Lambda_list = 1:2:10; + +Db_list = [-20:5:10] ; +Lambda_list = 10.^(Db_list/10) ; % spatial filter test parameters fund_freq_idx = 1 ; From 0061731f64b6f01c60f0383748a152a08f0c5aef Mon Sep 17 00:00:00 2001 From: sebobosse Date: Tue, 8 Jan 2019 11:58:36 +0100 Subject: [PATCH 56/62] aligned number of harmonics used to define input snr and to estimate snr of recovered component --- Examples/test_spatial_filters_2sources.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Examples/test_spatial_filters_2sources.m b/Examples/test_spatial_filters_2sources.m index f0e65f5..2a0b363 100644 --- a/Examples/test_spatial_filters_2sources.m +++ b/Examples/test_spatial_filters_2sources.m @@ -121,7 +121,7 @@ source_pattern = Source_pattern(:,:,subj_idx ); decomp_methods = {'pca','ssd','csp','rca'} ; - considered_harms=[1,2] ; + considered_harms = snr_harmonics ; for nTrial_idx = 1:length(numTrials_list) nUsedTrials = numTrials_list(nTrial_idx); From 14b84206aca515c0479c663e0fa06858ad8fbf91 Mon Sep 17 00:00:00 2001 From: sebobosse Date: Tue, 8 Jan 2019 12:08:39 +0100 Subject: [PATCH 57/62] minor change for easier debugging --- Examples/test_spatial_filters_2sources.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Examples/test_spatial_filters_2sources.m b/Examples/test_spatial_filters_2sources.m index 2a0b363..42712d4 100644 --- a/Examples/test_spatial_filters_2sources.m +++ b/Examples/test_spatial_filters_2sources.m @@ -187,7 +187,7 @@ % calculate error angles freqs = [0:thisDecompAxx.nFr]*thisDecompAxx.dFHz; signal_freq_idxs = find(ismember(freqs,thisFundFreq*considered_harms)); - noise_freq_idxs = [signal_freq_idxs-1,signal_freq_idxs+1] ; + noise_freq_idxs = reshape([signal_freq_idxs-1;signal_freq_idxs+1],1,[]) ; %err_angles.(this_decomp_method)(comp_idx,nTrial_idx,draw_idx) = 180/pi* acos(abs(source_pattern(:,1)'*thisA(:,comp_idx))/sqrt(sum(source_pattern(:,1).^2)*sum(thisA(:,comp_idx).^2))) ; err_angles.(this_decomp_method){s}(:,1:size(thisA,2),nLambda_idx,draw_idx) = 180/pi* acos(abs(source_pattern'*thisA)./sqrt(repmat(sum(source_pattern.^2)',[1 size(thisA,2)]).*repmat(sum(thisA.^2),[size(source_pattern,2) 1]))) ; From 16d13ce14f65c721a52851056a3a9269e2f9b603 Mon Sep 17 00:00:00 2001 From: sebobosse Date: Tue, 8 Jan 2019 16:11:49 +0100 Subject: [PATCH 58/62] bugfix: removed factor 2 from snr calculation --- Examples/test_spatial_filters_2sources.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Examples/test_spatial_filters_2sources.m b/Examples/test_spatial_filters_2sources.m index 42712d4..fef3036 100644 --- a/Examples/test_spatial_filters_2sources.m +++ b/Examples/test_spatial_filters_2sources.m @@ -199,7 +199,7 @@ % end %calculate snrs assuming ssveps, mean over all trials - snrs.(this_decomp_method){s}(1:size(thisA,2),nLambda_idx,draw_idx)=mean(2*mean(thisDecompAxx.Amp(signal_freq_idxs,:,:).^2)./mean(thisDecompAxx.Amp(noise_freq_idxs,:,:).^2),3); + snrs.(this_decomp_method){s}(1:size(thisA,2),nLambda_idx,draw_idx)=mean(mean(thisDecompAxx.Amp(signal_freq_idxs,:,:).^2)./mean(thisDecompAxx.Amp(noise_freq_idxs,:,:).^2),3); % calculate residuals as mse over samples and trials % TODO: needs some sort of normalization!! est_signal = squeeze(thisDecompAxx.Wave ); From cb15ae69c5a0fe1148c47c3d04c9820da372e5d5 Mon Sep 17 00:00:00 2001 From: sebobosse Date: Wed, 9 Jan 2019 20:53:19 +0100 Subject: [PATCH 59/62] corrections for residual calculation --- Examples/test_spatial_filters_2sources.m | 25 +++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/Examples/test_spatial_filters_2sources.m b/Examples/test_spatial_filters_2sources.m index fef3036..3e4cee5 100644 --- a/Examples/test_spatial_filters_2sources.m +++ b/Examples/test_spatial_filters_2sources.m @@ -200,21 +200,24 @@ %calculate snrs assuming ssveps, mean over all trials snrs.(this_decomp_method){s}(1:size(thisA,2),nLambda_idx,draw_idx)=mean(mean(thisDecompAxx.Amp(signal_freq_idxs,:,:).^2)./mean(thisDecompAxx.Amp(noise_freq_idxs,:,:).^2),3); + + % residual (mse over sampmles averaged and trials) % calculate residuals as mse over samples and trials - % TODO: needs some sort of normalization!! - est_signal = squeeze(thisDecompAxx.Wave ); - ref_signal = outSignal(1:100,:);%squeeze(repmat(EEGAxx_signal{1}.Wave(:,1,:),1,1,size(thisDecompAxx.Wave,3))) ; + est_signal = thisDecompAxx.Wave ; + ref_signal = outSignal(1:100,:); % normalize to equal power before calculating residual est_signal = est_signal./sqrt(mean(est_signal.^2,1)); - est_signal = repmat(mean(est_signal,3),[1 1 size(ref_signal,2)]); - ref_signal = ref_signal./sqrt(mean(ref_signal.^2,1)); - ref_signal = permute(repmat(ref_signal,[1 1 size(est_signal,2)]),[1 3 2]); - - residuals.(this_decomp_method){s}(:,1:size(thisA,2),nLambda_idx,draw_idx) =... - squeeze(min(... - mean((ref_signal-est_signal).^2),... - mean((ref_signal+est_signal).^2)))'; + + ref_signal = permute(repmat(ref_signal,[1,1,thisDecompAxx.nCh,thisDecompAxx.nTrl]),[1,3,4,2]) ; + est_signal = repmat(est_signal,[1,1,1, size(outSignal,2)]); + + trialwise_res_pos = mean((ref_signal-est_signal).^2) ; + trialwise_res_neg = mean((ref_signal+est_signal).^2) ; % in case the sign is flipped + trialwise_res = squeeze(min([trialwise_res_neg; trialwise_res_pos])) ; + + residuals.(this_decomp_method){s}(:,1:size(thisA,2),nLambda_idx,draw_idx) =squeeze(mean(trialwise_res,2))'; % average residual over trials + end end end From e1bc834e1b1bcdff2ab7eb148900662428732d2e Mon Sep 17 00:00:00 2001 From: sebobosse Date: Thu, 24 Jan 2019 21:54:18 +0100 Subject: [PATCH 60/62] corrections in values of coherence; aligned data structure of coherence model --- .../private/SpatialDecayofCoherence.m | 83 +++++++++++-------- 1 file changed, 48 insertions(+), 35 deletions(-) diff --git a/+mrC/+Simulate/private/SpatialDecayofCoherence.m b/+mrC/+Simulate/private/SpatialDecayofCoherence.m index c14a50e..3188b95 100644 --- a/+mrC/+Simulate/private/SpatialDecayofCoherence.m +++ b/+mrC/+Simulate/private/SpatialDecayofCoherence.m @@ -1,7 +1,7 @@ % estimate and write out functional model and parameters for -% distance-dependence of coherence (according to Fig. 4 from 'Multi-scale +% distance-dependence of coherence (according to MAE1 numbers Fig. 4 from 'Multi-scale % analysis of neural activity in humans: Implications for -% micro-scale electrocorticography' +% micro-scale electrocorticography') clear all model_fun.gauss = @(p,x)(p(4)+p(1)*exp(-((x-p(3))/2*p(2)^2))); @@ -9,25 +9,36 @@ model_fun.lorentzian = @(p,x)(p(4)+p(1)* ((p(2)^2)./(p(2)^2+(x-p(3)).^2))); model_fun.power_law = @(p,x)(p(4)+p(1)* (x+p(3)).^(-p(2))); + + % data taken from Fig. 4 -x = [0,0.38,0.5,0.54,1:0.5:3] % in mm +x = [0,0.38,0.5,0.54,1:0.5:3] ;% in mm y.delta = [7., 6.3, 6.1, 6.05, 5.5, 5.3, 5, 4.9, 4.8]/7; % strange number where read from print in cm y.theta = [7., 6.1, 5.8, 5.7, 5.1, 4.8, 4.5, 4.4, 4.3]/7; y.beta = [7., 6.3, 6.1, 6.05, 5.75, 5.5, 5.25, 5.1, 5]/7; y.alpha = [7., 5.1, 4.9, 4.8, 4.45, 4.2, 3.9, 3.8, 3.6]/7; y.gamma = [7., 3.2, 2.9, 2.75, 2.3, 2.05, 1.9, 1.75, 1.6]/7; -band_freqs{1} = [0,4]; -band_freqs{2} = [4,8]; -band_freqs{3} = [8,12]; -band_freqs{4} = [12,30]; -band_freqs{5} = [30,80]; +x = [0., 0.38, 0.5, 0.54, 1., 1.5, 2., 2.5, 3., 3.5, 4., 4.5, 5.] ;% in mm +y.delta = [1., 0.85, 0.825, 0.81, 0.72, 0.67, 0.62, 0.61, 0.61, 0.61, 0.61, 0.61, 0.61] ; +y.theta = [1., 0.9, 0.87, 0.85, 0.8, 0.73, 0.7, 0.67, 0.67, 0.67, 0.67, 0.67, 0.67] ; +y.beta = [1., 0.73, 0.7, 0.68, 0.63, 0.6, 0.55, 0.63, 0.5, 0.48, 0.46, 0.44, 0.4 ]; +y.alpha = [1., 0.9, 0.87, 0.81, 0.82, 0.79, 0.75, 0.725, 0.71, 0.68, 0.66, 0.62, 0.6 ] ; +y.gamma = [1., 0.45, 0.42, 0.39, 0.33, 0.3 , 0.27, 0.25, 0.22, 0.21, 0.2, 0.2, 0.19]; + + +band_freqs.delta = [0,4]; +band_freqs.theta = [4,8]; +band_freqs.alpha = [8,12]; +band_freqs.beta = [12,30]; +band_freqs.gamma = [30,80]; +%% fig=figure ; freq_band_names = fieldnames(y) ; model_names = fieldnames(model_fun) ; -x_temp = [0:0.5:4]; +x_temp = [min(x):0.1:100*max(x)]; colors = {'r','g','b','k','c','m','y'}; linestyles = {'-','--',':','-.'}; @@ -35,7 +46,7 @@ handles = []; for i = 1:length(freq_band_names) this_bands_mse = 100000; - + this_band_name = freq_band_names{i}; for this_model_idx = 1:length(model_names) subplot(1,length(model_names),this_model_idx ); this_model_name = model_names{this_model_idx} ; @@ -43,28 +54,29 @@ hold on if strcmp(this_model_name,'power_law') if strfind(version,'R2011') - [this_p,R,J,CovB,MSE] =nlinfit(x(1:end),y.(freq_band_names{i})(1:end),model_fun.(this_model_name),[1.,1.,1. ,0.]); + [this_p,R,J,CovB,MSE] =nlinfit(x(1:end),y.(this_band_name)(1:end),model_fun.(this_model_name),[1.,1.,1. ,0.]); else - [this_p,R,J,CovB,MSE,ErrorModelInfo] =nlinfit(x(1:end),y.(freq_band_names{i})(1:end),model_fun.(this_model_name),[1.,1.,1. ,0.]); + [this_p,R,J,CovB,MSE,ErrorModelInfo] =nlinfit(x(1:end),y.(this_band_name)(1:end),model_fun.(this_model_name),[1.,1.,1. ,0.]); end else if strfind(version,'R2011') - [this_p,R,J,CovB,MSE] =nlinfit(x(1:end),y.(freq_band_names{i})(1:end),model_fun.(this_model_name),[1.,1.,0 ,0.]); + [this_p,R,J,CovB,MSE] =nlinfit(x(1:end),y.(this_band_name)(1:end),model_fun.(this_model_name),[1.,1.,0 ,0.]); else - [this_p,R,J,CovB,MSE,ErrorModelInfo] =nlinfit(x(1:end),y.(freq_band_names{i})(1:end),model_fun.(this_model_name),[1.,1.,0 ,0.]); + [this_p,R,J,CovB,MSE,ErrorModelInfo] =nlinfit(x(1:end),y.(this_band_name)(1:end),model_fun.(this_model_name),[1.,1.,0 ,0.]); end end - model_params.(freq_band_names{i}).(this_model_name) = this_p; - h=plot(x_temp,model_fun.(this_model_name)( model_params.(freq_band_names{i}).(this_model_name) ,x_temp),colors{i}); + model_params.(this_band_name).(this_model_name) = this_p; + h=plot(x_temp,model_fun.(this_model_name)( model_params.(this_band_name).(this_model_name) ,x_temp),colors{i}); if this_model_idx==1 handles = [handles,h]; end if MSE Date: Tue, 29 Jan 2019 17:20:07 +0100 Subject: [PATCH 61/62] more appropriate fitting functions for estimating spatial decay of coherence --- .../private/SpatialDecayofCoherence.m | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/+mrC/+Simulate/private/SpatialDecayofCoherence.m b/+mrC/+Simulate/private/SpatialDecayofCoherence.m index 3188b95..a4e92c8 100644 --- a/+mrC/+Simulate/private/SpatialDecayofCoherence.m +++ b/+mrC/+Simulate/private/SpatialDecayofCoherence.m @@ -4,20 +4,23 @@ % micro-scale electrocorticography') clear all -model_fun.gauss = @(p,x)(p(4)+p(1)*exp(-((x-p(3))/2*p(2)^2))); -model_fun.expo = @(p,x)(p(4)+p(1)*exp(-p(2)*(x-p(3)))); -model_fun.lorentzian = @(p,x)(p(4)+p(1)* ((p(2)^2)./(p(2)^2+(x-p(3)).^2))); -model_fun.power_law = @(p,x)(p(4)+p(1)* (x+p(3)).^(-p(2))); +% model_fun.gauss = @(p,x)(p(4)+p(1)*exp(-((x-p(3))/2*p(2)^2))); +% model_fun.expo = @(p,x)(p(4)+p(1)*exp(-p(2)*(x-p(3)))); +% model_fun.lorentzian = @(p,x)(p(4)+p(1)* ((p(2)^2)./(p(2)^2+(x-p(3)).^2))); +% model_fun.power_law = @(p,x)(p(4)+p(1)* (x+p(3)).^(-p(2))); +model_fun.expo = @(p,x)( (1-p(1)) *exp(-p(2)*x) ); +model_fun.lorentzian = @(p,x)( (1-p(1))*(p(2)^2./(p(2)^2+x.^2)) ) ; +model_fun.gauss = @(p,x) ( (1-p(1)) * exp(-(x.^2) )/p(2) ) ; +model_fun.power_law = @(p,x) ( (p(2)*x+1).^-p(1)) ; - -% data taken from Fig. 4 -x = [0,0.38,0.5,0.54,1:0.5:3] ;% in mm -y.delta = [7., 6.3, 6.1, 6.05, 5.5, 5.3, 5, 4.9, 4.8]/7; % strange number where read from print in cm -y.theta = [7., 6.1, 5.8, 5.7, 5.1, 4.8, 4.5, 4.4, 4.3]/7; -y.beta = [7., 6.3, 6.1, 6.05, 5.75, 5.5, 5.25, 5.1, 5]/7; -y.alpha = [7., 5.1, 4.9, 4.8, 4.45, 4.2, 3.9, 3.8, 3.6]/7; -y.gamma = [7., 3.2, 2.9, 2.75, 2.3, 2.05, 1.9, 1.75, 1.6]/7; +% % data taken from Fig. 4 +% x = [0,0.38,0.5,0.54,1:0.5:3] ;% in mm +% y.delta = [7., 6.3, 6.1, 6.05, 5.5, 5.3, 5, 4.9, 4.8]/7; % strange number where read from print in cm +% y.theta = [7., 6.1, 5.8, 5.7, 5.1, 4.8, 4.5, 4.4, 4.3]/7; +% y.beta = [7., 6.3, 6.1, 6.05, 5.75, 5.5, 5.25, 5.1, 5]/7; +% y.alpha = [7., 5.1, 4.9, 4.8, 4.45, 4.2, 3.9, 3.8, 3.6]/7; +% y.gamma = [7., 3.2, 2.9, 2.75, 2.3, 2.05, 1.9, 1.75, 1.6]/7; x = [0., 0.38, 0.5, 0.54, 1., 1.5, 2., 2.5, 3., 3.5, 4., 4.5, 5.] ;% in mm @@ -33,12 +36,12 @@ band_freqs.alpha = [8,12]; band_freqs.beta = [12,30]; band_freqs.gamma = [30,80]; -%% +% fig=figure ; freq_band_names = fieldnames(y) ; model_names = fieldnames(model_fun) ; -x_temp = [min(x):0.1:100*max(x)]; +x_temp = [min(x):0.1:2000*max(x)]; colors = {'r','g','b','k','c','m','y'}; linestyles = {'-','--',':','-.'}; @@ -65,9 +68,10 @@ [this_p,R,J,CovB,MSE,ErrorModelInfo] =nlinfit(x(1:end),y.(this_band_name)(1:end),model_fun.(this_model_name),[1.,1.,0 ,0.]); end end - + sprintf('band: %s, model: %s, MSE=%f, mean(R)= %f',this_band_name,this_model_name, MSE,mean(R.^2)) model_params.(this_band_name).(this_model_name) = this_p; h=plot(x_temp,model_fun.(this_model_name)( model_params.(this_band_name).(this_model_name) ,x_temp),colors{i}); + xlim([0,15]) if this_model_idx==1 handles = [handles,h]; end From 8f17ed9ae408882939393f54276372782b8e1c59 Mon Sep 17 00:00:00 2001 From: sebobosse Date: Wed, 20 Feb 2019 10:00:55 +0100 Subject: [PATCH 62/62] spatial coherence models base on fig.3 supplementary in kellis et al. 2016 --- .../private/SpatialDecayofCoherence.m | 44 +++++++++++------- .../spatial_decay_models_coherence.mat | Bin 2593 -> 1815 bytes 2 files changed, 27 insertions(+), 17 deletions(-) diff --git a/+mrC/+Simulate/private/SpatialDecayofCoherence.m b/+mrC/+Simulate/private/SpatialDecayofCoherence.m index a4e92c8..fcfe973 100644 --- a/+mrC/+Simulate/private/SpatialDecayofCoherence.m +++ b/+mrC/+Simulate/private/SpatialDecayofCoherence.m @@ -9,10 +9,10 @@ % model_fun.lorentzian = @(p,x)(p(4)+p(1)* ((p(2)^2)./(p(2)^2+(x-p(3)).^2))); % model_fun.power_law = @(p,x)(p(4)+p(1)* (x+p(3)).^(-p(2))); -model_fun.expo = @(p,x)( (1-p(1)) *exp(-p(2)*x) ); -model_fun.lorentzian = @(p,x)( (1-p(1))*(p(2)^2./(p(2)^2+x.^2)) ) ; -model_fun.gauss = @(p,x) ( (1-p(1)) * exp(-(x.^2) )/p(2) ) ; -model_fun.power_law = @(p,x) ( (p(2)*x+1).^-p(1)) ; +model_fun.expo = @(p,x)( (1) *exp(-p(1)*x) ); +% model_fun.lorentzian = @(p,x)( (1)*(p(1)^2./(p(1)^2+x.^2)) ) ; +% model_fun.gauss = @(p,x) ( (1) * exp(-(x.^2) )/p(1) ) ; +% model_fun.power_law = @(p,x) ( (p(2)*x+1).^-p(1)) ; % % data taken from Fig. 4 % x = [0,0.38,0.5,0.54,1:0.5:3] ;% in mm @@ -22,14 +22,21 @@ % y.alpha = [7., 5.1, 4.9, 4.8, 4.45, 4.2, 3.9, 3.8, 3.6]/7; % y.gamma = [7., 3.2, 2.9, 2.75, 2.3, 2.05, 1.9, 1.75, 1.6]/7; - -x = [0., 0.38, 0.5, 0.54, 1., 1.5, 2., 2.5, 3., 3.5, 4., 4.5, 5.] ;% in mm -y.delta = [1., 0.85, 0.825, 0.81, 0.72, 0.67, 0.62, 0.61, 0.61, 0.61, 0.61, 0.61, 0.61] ; -y.theta = [1., 0.9, 0.87, 0.85, 0.8, 0.73, 0.7, 0.67, 0.67, 0.67, 0.67, 0.67, 0.67] ; -y.beta = [1., 0.73, 0.7, 0.68, 0.63, 0.6, 0.55, 0.63, 0.5, 0.48, 0.46, 0.44, 0.4 ]; -y.alpha = [1., 0.9, 0.87, 0.81, 0.82, 0.79, 0.75, 0.725, 0.71, 0.68, 0.66, 0.62, 0.6 ] ; -y.gamma = [1., 0.45, 0.42, 0.39, 0.33, 0.3 , 0.27, 0.25, 0.22, 0.21, 0.2, 0.2, 0.19]; - +% from microEcog3, fig. 3 in supplementary material +% x = [0., 0.38, 0.5, 0.54, 1., 1.5, 2., 2.5, 3., 3.5, 4., 4.5, 5.] ;% in mm +% y.delta = [1., 0.85, 0.825, 0.81, 0.72, 0.67, 0.62, 0.61, 0.61, 0.61, 0.61, 0.61, 0.61] ; +% y.theta = [1., 0.9, 0.87, 0.85, 0.8, 0.73, 0.7, 0.67, 0.67, 0.67, 0.67, 0.67, 0.67] ; +% y.beta = [1., 0.73, 0.7, 0.68, 0.63, 0.6, 0.55, 0.63, 0.5, 0.48, 0.46, 0.44, 0.4 ]; +% y.alpha = [1., 0.9, 0.87, 0.81, 0.82, 0.79, 0.75, 0.725, 0.71, 0.68, 0.66, 0.62, 0.6 ] ; +% y.gamma = [1., 0.45, 0.42, 0.39, 0.33, 0.3 , 0.27, 0.25, 0.22, 0.21, 0.2, 0.2, 0.19]; + +%% data taken from micro ECoG3 in suplimentary material +x = [ 0., 0.5, 1., 1.5, 2., 2.5, 3., 3.5, 4.] ; +y.delta = [ 1., 0.96, 0.94, 0.9, 0.88, 0.84, 0.8, 0.78, 0.76 ]; +y.theta = [ 1., 0.96, 0.94, 0.9, 0.88, 0.84, 0.82, 0.8, 0.78 ]; +y.alpha = [ 1., 0.95, 0.91, 0.86, 0.84, 0.78, 0.72, 0.67, 0.61]; +y.beta = [ 1., 0.92, 0.84, 0.76, 0.68, 0.61, 0.69, 0.56, 0.48] ; +y.gamma = [ 1., 0.84, 0.7, 0.68, 0.48, 0.4, 0.36, 0.32, 0.28]; band_freqs.delta = [0,4]; band_freqs.theta = [4,8]; @@ -37,7 +44,7 @@ band_freqs.beta = [12,30]; band_freqs.gamma = [30,80]; % -fig=figure ; +fig_all_fits=figure ; freq_band_names = fieldnames(y) ; model_names = fieldnames(model_fun) ; @@ -71,7 +78,7 @@ sprintf('band: %s, model: %s, MSE=%f, mean(R)= %f',this_band_name,this_model_name, MSE,mean(R.^2)) model_params.(this_band_name).(this_model_name) = this_p; h=plot(x_temp,model_fun.(this_model_name)( model_params.(this_band_name).(this_model_name) ,x_temp),colors{i}); - xlim([0,15]) + xlim([0,5]) if this_model_idx==1 handles = [handles,h]; end @@ -110,7 +117,7 @@ lw = 1.5; % LineWidth msz = 8; % MarkerSize - x_temp = 0:0.01:5 ; + x_temp = 0:0.01:150 ; colors = brewermap(length(freq_band_names),'Set2'); fig = figure; legend_entries = {} ; @@ -124,7 +131,7 @@ xlabel('Spatial Distance [mm]') ylabel('Coherence') ylim([0,1]) - xlim([0,5]) + xlim([0,70]) legend(legend_entries,'FontSize',fsz,'location','northeastoutside') set(gca, 'FontSize', fsz, 'LineWidth', alw); %<- Set properties @@ -138,6 +145,9 @@ fig.Position = [0.0, 0.0, fig_width, fig_height]; fig.Resize = 'off'; fig.InvertHardcopy = 'off'; - filename = fullfile(plot_dir,'spatial_decay_coherence'); + filename = fullfile(plot_dir,'spatial_decay_coherence_expo_only'); + print(filename,'-dpdf'); + filename = fullfile(plot_dir,'spatial_decay_coherence_all_fitFuncs_smallRange'); + figure(fig_all_fits) print(filename,'-dpdf'); end \ No newline at end of file diff --git a/+mrC/+Simulate/private/spatial_decay_models_coherence.mat b/+mrC/+Simulate/private/spatial_decay_models_coherence.mat index b460b4e843cce6c39f756b720ced11dc49998b1e..5288fb8215215183dfc87aea5cf2bfa40e612898 100644 GIT binary patch delta 1705 zcmV;a23Gl@6qgQ=GZ;;8ZXiZwVjwX%ATc*OGB-LhF(5K9F*%V@BavVRf7t~900000 z0003=O%DJ70CWHV0C=3^V_;xtU}Rum1JWEo%)|fztWZ8966ONpq{O_G__U(b!eW>@ z7O-BByc`gxq~??)GL&Sb0?EXjf{a9lBoLD!Jux>INHOpMu>k|ZJ~k}&F+phtFdN8Y zAx|F*2YLE9c*xVoBS)S-f4Kk#1^{`q5rGc?004Ia004NL?UcW76EPIW-=&vAky@n- z6oeoPB7xFNSs0rT1}e1z5jr3tot(t!9o(I*v(qLLNK7Ck{s2Zc7(uWxF~E)x3ld}Z z4opngxpR8Cgr?al#g^XL&p*D;e)sfQ0DzO{0A^(`ECW%?#=eoTe^bKhm~q=DP3)U6|Ul!I2ZBrB?pFyh~K)*#R z0rD}#aqhxFoY@g^%tV|yVR>Pv-+p)_W&BcCM zz6x;<)k8vfw9c@6e>*23C_mtCp#BEPlr*?Y0^31B)5Ak~Fh|6{n24W?eXByL=Qfbk zV%BEm{zm1deeZf{^+tUKQQFfjjnOp*bPcfLd`x<-9G`oanJkZsvA>nbi^>!5F2#^+ zE!P{I*d^g(Mp2_byX$ht4Q#nlR3LW%hU;w9d(NlwD}C0we^e?L=yIo6D6AH*;0`TZ zqjJ=`TgY-x(OJ7h{Cps63rX^Nz)4L^^kTC3L&1yJMLc+bXhhDNvXs!c+cw+sck?lP1YgQ` z@DbcDU0hZaX)-(grqgex-%betW_f@@!{ge|;o%%LY5TFRXmr4>?zhb4dgavEm^lm?_GLZy-c3@D%0D z9bq=`NnHOCzU$}L)IUkpFB^=SLip6fnoFWi^SRx;b)T;5jhlWA3la0yH}G03ycQs% z$F}zzbMHO3osJcQdnsKKoj}b6f%FE`Wf8mQ1*jq-YRE;Qeb)qY<8iDC+lfK6p zeKnTDv$|u8%URX78`bk@yzDEXce83W;dM6f7BjpWW5(G$;A3gxn&))`Pdme-G3rSY zO4R345;Pzav=~Gbge`Otf4Vdn`&bbqLORjD8)-tZe@8q#=ff5sk&rNt)Pkgsw^Sb$ zNmI{S`aE=64_%Fq;Kv-h*`J~L?o)jGnRgXnbsjW6Tx>sbsk0<6%ePLagN#lZ|MF<~ z0%_TLFAo3!0ImQ40C=3^V_;zDU}lrs0x^GLPC-T@LlTI|ke-;E3#1tMfOrN2arVK? zXNB@XbZSLGJ_FR803>tRu$aSwWDX|~D*&<}+^DG!h%P%$P?iqFe zsQX9VKXl!{f<*TdvX6lQzkQVI??BRzt$h%Iq@D@NM{XY=hd(9l0}ucJfk8D1#(@d{ delta 2509 zcmV;;2{QJV4xto~GZ<8LWgtdnVjwawATcpIF)%taGaxcBF*uP?BavVRfAL-CqIv!(zocC=`JQ6Suw5EYWZC6PM-?SSzj9NWHZ(Vzo{|uu1 z#gzOTvHT^a%@*gb(PC#^&4yiwoT?bHb8^1U+R8e?${e=xNlQ|@c3eecF} zJ}xSaV!i+bPux~sIj$NFnD^jw(v^PQWYlLS{1V!~+pIScuMRQdXb)F7+QH@UM4@ej zhT9zNVr(|b95wKD17keO)6B@TkjNu6p^QE~vc5XAnSSaPkE8d!#P+Qw))`@>_R;Bu z(ChH+Tv?H0QM}_=f4t>H9dn8ABlMaazRtk-_cI{pEawaWC;Sq6GG~IVvStlqwJ+x^ zoijZa@p@Boc93)SC!Br!6KB1Q`t}ok37yIrLh1smnQBzgz}4inmh%SbycHng?P^Ni zP|n+*@b>mkyj^9~x0Ucq=ycwO6$O{P-tzy(iF_5+^AlDDe>gM`0000`0001Zoa19) zU(Ux8!#aBvtiNCgk&xgh{r;n zJ{Au0^l|Wzr;kUDJbiKj3=9CdDhFW?0000>0{{Scoa19)V5s0^U|<8%96-#(00FE} zJ|hz50pi^JlSl;}e-embNX#k7NMuM)%*_RI82Etr1S4_w!OZ7|@^yeXJ+ZX77$TKg zQIHQ+m6KnTnpaYlnV82=kYAo!6rYn=4v_)b7r=n9j}41`EJ*eNgMvW;h<~2;lY98{ zxqX)W=918~Z}ziNHqGw8`fC5~!YiGxN}k%2Yi`-935O28e|l(t`E`r!&*=B|bt|4s z$O(G5|J2;=O_Z1`HS2Hp`Xi6*)may_>fZcl&lo;~^J?Xr{r9~cABgE&9@a-Mb6kr=EVd-|EhE zW_$kQ{Rf4ne}$TQJSIDQK1WOX1h_r1Uw!WE{UbL%+WS42erx~oTl*&^91W(#+#0<- zzD7$Q+4t=eSUAD=o&Bq~UfWL3dbGdqt{C69sz>CfzatmyAI;HoaNheeS}c&uVVU(b zuGI_K9qwz#seLzjG-Tpa;e`YLtX0qLpB(Fm?r;2Nf3Mt@zv1Yucl&$0J8FU#JtI4O zbj~k|ubBV9e$T;%thg2L?e9K%D_@=acz^S%(*l&3yEJ63m)~YZhqYG@Gbru8VgLDR z^kRXu|NB|qOVu>{k)Q8*KQG+EwVuyGV!`gi$7Sc)#~m#^{=52u{VnrV-51}n9~dg} z8I@V*f10}Gt-V$E>A$-j7#vPCEZP6^(Y5_@-!F(CZ+S>|_^f&J@0pj%3w!wwH_6zO6=KX_ zaWIvY{<+ra;C|u1wW2$}Um(Bya?QEO`0B0w#_i21OW3#^iu$^8{=078AA0L%_YMlo zy?gokY0g=84(az+y*Zn|$^I{MVz=DQJNvsgzM6O`?i|_XavYU$GqSv3&z|q^|4rep ze|^H@1Hbzs*V*5y`7pW5WUD;@biu7-4*&oF(gFYgc%0*7U|{$m!oa`=q&a|?i2(vw zp?pRp%mu_rsl_Gnx%nxnIWToBV7(xDIUoi~lq52gWTXPgBoM)nm{X9E$dI0xn+xPH z@By&|3vu?r%;$yjrGOZ0b$mf$QDSZ}e?wYn9s?QxnIFJ_FrN*J`7B80a{{pf5SOi* zaOmKxhxV6Wx7hxSes5p5;>m=ZpojZU&E4Mgs^qCX$lL^Ggt-C)+znE1z)YGy1)zKf zkUtYkauSn@^7Bg|^2MnjKbPbeF~InFC7F4!SYQAJMRG}IeqMY=VqQv4Dn>Z4e_*j+ z9?5PpdcqRIT2)5vA(Zkh>xRFe2|fWp>dL4VnG4KUj)o$L@}3U+URS~zk+`5H*!!oW~cS&#}1tIQlIt31CbySN}R84_Lz1l*^I7G5BJahj@ugGPZ) zg{DSI)&v5mK9D^Q1nl8Nvj@aa%*)TK%*`(? zMluIqc)|d(IWT=Qkn{Do4B|^NV5F;fM2I2G^FT6>7tI~8vH@m3h%W(Q z-ep3PgRw#8DGb8$p+;|yuhG&+_I>*V7EbVeXaDN0*S6EM9_{bDE5`S&f9esqe8?cN ze6ScTAE;73e4tbLkU_wGW@zCB;$tlzG&DekL!6POo<5YN4J#=0fTEznVzivVUQRGG zAj%1WL0C@keqOkRYdxQX#Dd+2kIT-pk2_j;{CD*Q`&;I#x-Y(CKL9c}g2Zw{W3-&0 zN;&a>PUS=d0r#n+g%^mAf4!Wj(1sQYkmdxiU>GeIu$K$4yf|3P1=*(cpR;pB9dyjJ z?VGPpwilh~BetJ&q5Z?(LS>1MghtDS(Q;w5To{4n0>@DqHzUgn_U!ro{@)bt+9xbN z@VhT^o&Bwv50kr0w%U)D3!~-2Xt_YUasdEx-kr$p8VYP(C9N&tQYlAixCWGcbDkg7|zu{DT#tp9zb87>0umYI_pUX)l+kXjTU57l3Qq#t1)gwKxV4v_i|M%@06U_@2V z1Qla~vORz}1LS5Pf7Sqs8!$rTVIVQDk|C`$FS#T$KQBHbF)t-2HNHH*D7&~IF*%h1 zJ7B^hh8}*5crX(hpBaZeFmuq^jA-IaNHnrIGZKv~&Vojh0{D>V9SpeR5!sJ0^&Uv- zdC}Cfp{WP)B_PbZ=w>lO&9h)2Ej^>VXVm?p?x)24D@b%dBO&`381UOiss0WI{Pjl! Xl6od6AG!WO4u4AO4-fzVn-(pa@Egq?