[FieldTrip] [FORGED] Re: Computing source-level ROI EEG connectivity

Xavier Vrijdag x.vrijdag at auckland.ac.nz
Sun May 17 01:01:10 CEST 2020


Hello Michael,

I currently following a very similar path for analysing my EEG data with the following steps:

%Load the template brain, MRI and EEG electrodes
load ('biosemi32elec_aligned.mat') % electrodes have been manually aligned using the interface
load ('leadfield6mm.mat')
load([FIELDTRIPDIR '/template/headmodel/standard_bem.mat']); %template boundary element model
load([FIELDTRIPDIR  '/template/headmodel/standard_mri.mat']); %template mri
atlas = ft_read_atlas([FIELDTRIPDIR '/template/atlas/aal/ROI_MNI_V4.nii']);

%Generate sourcemodel and leadfields (common for all data)

    cfg = [];

    cfg.headmodel = vol;

    cfg.elec = elec_aligned;

    cfg.grid.resolution = 6;   % use a 3-D grid with a 6mm resolution

    cfg.grid.unit       = 'mm';

    cfg.channel = 1:32;

    grid = ft_prepare_leadfield(cfg);
Load the data

%Calculate the channel covariance matrix for all exposures
        cfg = [];
        cfg.covariance = 'yes';
        cfg.covariancewindow = 'all';
        cfg.keeptrials  = 'yes';
        timelock_allexp = ft_timelockanalysis(cfg, data_filt);

%Calculate the channel covariance matric for each exposure separate
        timelock=cell(length(exposures));
        for j=1:length(exposures)
            cfg = [];
            cfg.covariance = 'yes';
            cfg.covariancewindow = 'all';
            cfg.trials = find(ismember(trialinfo, exposures{j}));
            cfg.keeptrials  = 'yes';
            timelock{j} = ft_timelockanalysis(cfg, data_filt);
        end

%General source localisation
        cfg = [];
        cfg.headmodel = vol;
        cfg.elec = elec_aligned;
        cfg.grid = grid;
        cfg.method = 'lcmv';
        cfg.lcmv.fixedori = 'yes';  %Project onto largest variance orientation
        cfg.lcmv.keepfilter = 'yes'; %Keep the beamformer weights
        cfg.lcmv.lambda = '5%'; %Regularise a little
        source_allexp = ft_sourceanalysis(cfg, timelock_allexp);

% source localisation for each exposure separate using combined filter
        cfg = [];
        cfg.headmodel = vol;
        cfg.elec = elec_aligned;
        cfg.grid = grid;
        cfg.grid.filter = source_allexp.avg.filter; % use the common filter computed in the previous step!
        cfg.method = 'lcmv';
        cfg.lcmv.fixedori = 'yes';  %Project onto largest variance orientation
        cfg.lcmv.lambda = '5%'; %Regularise a little
        cfg.rawtrial    = 'yes';      % project each single trial through the filter.
        cfg.keeptrials  = 'yes';

        parc_sources=cell(length(exposures),1);
        for j=1:length(exposures)
            source = ft_sourceanalysis(cfg, timelock{j});
            source = ft_datatype_source(source);

            cfg = [];
            cfg.parameter = 'all';
            source_int = ft_sourceinterpolate(cfg, source, atlas);

            cfg = [];
            cfg.method       = 'max';
            parc_sources{j}=ft_sourceparcellate(cfg, source_int, atlas);
        end

I hope my code for source parcellation helps to solve your problem.

Regards,

Xavier


From: fieldtrip <fieldtrip-bounces at science.ru.nl> on behalf of Michael Glassen - Biomedical Engineer <MGlassen at kesslerfoundation.org>
Reply to: FieldTrip discussion list <fieldtrip at science.ru.nl>
Date: Saturday, 16 May 2020 at 8:42 AM
To: FieldTrip discussion list <fieldtrip at science.ru.nl>
Subject: [FORGED] Re: [FieldTrip] Computing source-level ROI EEG connectivity


Dear Fieldtrip Community,



I implemented most of the changes you all suggested and some that you didn't, and I am still getting some odd results, but in a different way. At the end of my pipeline I am computing bivariate granger causality over all ROIs in the volumetric AAL atlas provided with fieldtrip. ft_sourceparcellate gives me errors when trying to use, so to project sources onto ROIs I interpolate the AAL atlas onto my sourcemodel using ft_sourceinterpolate. I then go through each ROI and create a binary mask of voxels, which I then use to average the moms together to get ROI source activity. Before this step I use the svd method to pick a principal component of each voxel. When I plot a heatmap of these results averaged over the beta band(12-30 Hz), most roi pairs have a granger value between 0.1-0.3, but there are certain rois that have a very high granger across a ton of regions(>1).  This happened with multiple datasets from different studies. I suspect that the issue(if these is one) in my pipeline is coming from how I am using the atlas to group together voxels into ROIs, but I am not sure. I have listed my process below with as much detail as possible, if you need any other information let me know:



Link to a matlab figure of granger results:

https://drive.google.com/open?id=1CCaTsw8VO0829GJsggBiB0LMquVRx4Ao



Step 1: Importing headmodel and EEG Data



My first step is loading in the default bem headmodel that comes shipped with fieldtrip, shown below:



headmodel =

  struct with fields:

     bnd: [1×3 struct]
    cond: [0.3300 0.0041 0.3300]
     mat: [3000×3000 double]
    type: 'dipoli'
    unit: 'mm'


I then import the EEG data(63 channels, 10-20 electrode layout) and convert the units to mm.


eegData = eeglab2fieldtrip(EEG,'preprocessing','none');

eegData.elec  = ft_convert_units(header.elec, 'mm');

eegData =

  struct with fields:

         elec: [1×1 struct]
      fsample: 1000
        trial: {1×113 cell}
         time: {1×113 cell}
        label: {1×63 cell}
    trialinfo: [113×7 table]
          cfg: [1×1 struct]

I then project the electrodes to the scalp using the code below. Plotting afterwards confirms the electrodes are properly aligned with the headmodel.

if strcmp(headmodel.type, 'bemcp')
    scalp_index = 3;
elseif strcmp(headmodel.type, 'dipoli')
    scalp_index = 1;
end

cfg = [];
cfg.method = 'project'; % onto scalp surface
cfg.headshape = headmodel.bnd(scalp_index); % scalp surface

eegData.elec = ft_electroderealign(cfg, eegData.elec);



Step 2: Calculate the foward solution:

In this step, I calculate the covariance for my data, and create the source model and calculate the leadfield.

cfg = [];
cfg.covariance = 'yes';
cfg.covariancewindow = [-inf 0];
cfg.keeptrials = 'yes';
tlckEeg = ft_timelockanalysis(cfg, eegData);

cfg             = [];
cfg.headmodel   = headmodel;
cfg.resolution  = 10;
cfg.inwardshift = 5;

sourcemodel = ft_prepare_sourcemodel(cfg);

cfg = [];
cfg.sourcemodel = sourcemodel;
cfg.headmodel   = headmodel;
cfg.elec        = tlckEeg.elec;

leadfield = ft_prepare_leadfield(cfg);

sourcemodel =

  struct with fields:

       dim: [13 18 14]
       pos: [3276×3 double]
      unit: 'mm'
    inside: [3276×1 logical]
       cfg: [1×1 struct]

leadfield =

  struct with fields:

                dim: [13 18 14]
                pos: [3276×3 double]
               unit: 'mm'
             inside: [3276×1 logical]
                cfg: [1×1 struct]
          leadfield: {1×3276 cell}
              label: {63×1 cell}
    leadfielddimord: '{pos}_chan_ori'




Step 3: Calculate the inverse solution

In this step, I calculate the sources using eloretta twice, once with the keepfilter option enabled and once with rawtrial enabled(this second run is used to obtain the correct structure format, the moms calculated from rawtrial are replaced with filter calculated moms). I then take the filters from the first run and multiply them with the original dataset to get the trial by trial mom data, which I use to replace the trial moms that were obtained with the rawtrial option. Then, for each voxel, I concanate all trial moms and calculate the principal component using svd. Once I have these I recalculate the moms for each trial using only the principal component.

cfg               = [];
cfg.method        = 'eloreta';
cfg.grid   = leadfield;
cfg.headmodel     = headmodel;
cfg.eloreta.lambda = 0.05;
cfg.keepmom = 'yes';
cfg.mne.keepmom = 'yes';
cfg.eloreta.keepfilter = 'yes';

sourceEEG          = ft_sourceanalysis(cfg,tlckEeg);

sourceEEG =

  struct with fields:

      time: [1×4000 double]
       dim: [13 18 14]
    inside: [3276×1 logical]
       pos: [3276×3 double]
    method: 'average'
       avg: [1×1 struct]
       cfg: [1×1 struct]


cfg               = [];
cfg.method        = 'eloreta';
cfg.grid   = leadfield;
cfg.headmodel     = headmodel;
cfg.eloreta.lambda = 0.05;
cfg.mne.projectnoise = 'yes';
cfg.keepmom = 'yes';
cfg.rawtrial = 'yes';
trialSource          = ft_sourceanalysis(cfg,tlckEeg);


trialSource =

  struct with fields:

      time: [1×4000 double]
       dim: [13 18 14]
    inside: [3276×1 logical]
       pos: [3276×3 double]
    method: 'rawtrial'
     trial: [1×113 struct]
        df: 113
       cfg: [1×1 struct]


%% Calculating 3 dimensional trial moms
for i = 1:length(sourceEEG.avg.filter)
    for i2 = 1:length(eegData.trial)

        if isempty(sourceEEG.avg.filter{i})

            x = [];

        else

            x = sourceEEG.avg.filter{i} * eegData.trial{i2}(:,:);

        end

        trialSource.trial(i2).mom{i} = x;

    end

end

%% Calculating the principal component for each voxel
for i2 = 1:length(trialSource.trial(1).mom)
    timeseries = [];

    for i = 1:length(trialSource.trial)
        timeseries = cat(2,timeseries,trialSource.trial(i).mom{i2});
    end

    [u{i2}, s{i2}, v{i2}] = svd(timeseries, 'econ');

end
%% Calculating 1dimensional trial moms
for i = 1:length(sourceEEG.avg.filter)
    for i2 = 1:length(eegData.trial)

        if isempty(sourceEEG.avg.filter{i})

            x = [];

        else

           x = u{i}(:,1)' *sourceEEG.avg.filter{i} * eegData.trial{i2}(:,:);

        end

        trialSource.trial(i2).mom{i} = x;

    end

end

Step 4: Create ROI masks from AAL atlas

In this step I load in the default AAL atlas included with fieldtrip. I then interpolate this atlas onto my sourcemodel. At this point I have tried to use ft_sourceparcellate to divide up my source data into ROIs, but I receive an error. Both the atlas and my head/source models are volumetric, and because I am using the template headmodel both objects are in MNI space, so I'm not sure how else to troubleshoot this part. Because of this error, I am using the interpolated atlas to create binary masks and averaging all moms for voxels in each ROI.
Code when I try to use ft_sourceparcellate:

atlas = ft_read_atlas(broddmanLoc);

atlas =

  struct with fields:

            dim: [91 109 91]
            hdr: [1×1 struct]
      transform: [4×4 double]
           unit: 'mm'
         tissue: [91×109×91 double]
    tissuelabel: {1×116 cell}
       coordsys: 'mni'

cfg = [];
cfg.method        = 'nearest';
cfg.parameter = 'tissue';
atlas2 = ft_sourceinterpolate(cfg,atlas,sourcemodel);

atlas2 =

  struct with fields:

          dim: [13 18 14]
    transform: [4×4 double]
         unit: 'mm'
       inside: [13×18×14 logical]
       tissue: [13×18×14 double]
          cfg: [1×1 struct]

% Here I try to use the original atlas with ft_sourceparcellate and receive an error
cfg = [];
cfg.method       = 'eig';
cfg.parcellation = 'tissue';
ft_sourceparcellate(cfg,trialSource,atlas)
Error using ft_sourceparcellate (line 109)
the source positions are not consistent with the parcellation, please use FT_SOURCEINTERPOLATE

% As you can see above, when I interpolate the atlas onto the source model, tissuelabel is missing in atlas2, so it is not recognized as a parcellation. When I add tissuelabel from atlas to atlas2, another error appears.

cfg = [];
cfg.method       = 'eig';
cfg.parcellation = 'tissue';
ft_sourceparcellate(cfg,trialSource,atlas2)
Error using ft_sourceparcellate (line 97)
This function requires parcellation data as input, see ft_datatype_parcellation.

atlas2.tissuelabel = atlas.tissuelabel;
cfg = [];
cfg.method       = 'eig';
cfg.parcellation = 'tissue';
ft_sourceparcellate(cfg,trialSource,atlas2)
Error using ft_sourceparcellate (line 97)
each index value should have a corresponding entry in "tissuelabel"

% Because of these errors, I instead create my own binary masks for each ROI, using methods from this tutorial:
http://www.fieldtriptoolbox.org/faq/how_can_i_map_source_locations_between_two_different_representations/

 atlas = ft_read_atlas(broddmanLoc);

cfg = [];
cfg.method        = 'nearest';
cfg.parameter = 'tissue';
atlas2 = ft_sourceinterpolate(cfg,atlas,sourcemodel);

roiData.time = eegData.time;
roiData.label = atlas.tissuelabel';
roiData.trial = {};

missingLabels = [];
for i = 1:length(atlas.tissuelabel)

    indx = find(atlas2.tissue==i);
    if isempty(indx)
        missingLabels = [missingLabels i];
    end

    for i3 = 1:length(trialSource.trial)
        timeseries = [];
        for i2 = 1:length(indx)
            try
                timeseries = cat(1,timeseries,trialSource.trial(i).mom{indx(i2)});
            end
        end

        if ~isempty(timeseries)
            for i4 = 1:size(timeseries,2)

                roiData.trial{i3}(i,i4) = mean(timeseries(:,i4));

            end
        else
            roiData.trial{i3}(i,:) = ones(1,length(roiData.time{1}))*10^-10;
        end
    end

end


Using this method, some ROIs from the atlas have zero indices found in the source model, which is confusing to me. I tried replace the source data for missing ROIs with a very small non-zero #(10^-10) so that the rest of the script runs. Looking at the indices now I realize that these are the indices with extrememly high granger values, so adding in small non-zero values is not a valid solution it seems. Is there any reason for these ROIs to not be showing up in my model? The missing indices are listed below. I assume that the issue is coming from the interpolation of the atlas.



missingLabels = [21 70 79 80 95 101 107 108 109]





Step 5: Calculate Connectivity



In this step I calculate granger causality. I don't think anything is going wrong here, after having gone through all of my code for this email I realize that the missing ROIs are the most likely culprit.


cfg            = [];
cfg.output     = 'fourier';
cfg.method     = 'mtmfft';
cfg.foilim     = [0 50];
cfg.tapsmofrq  = 4;
cfg.keeptrials = 'yes';
cfg.pad = 4;
freq    = ft_freqanalysis(cfg, roiData);

cfg = [];
cfg.method = 'granger';
cfg.granger.sfmethod = 'bivariate';
granger = ft_connectivityanalysis(cfg, freq);

Conclusions

After going through all of my code, I realize the main issue is that some of the atlas ROIs have no indices in my sourcemodel after interpolating. Is there any fix/advice that you can suggest to either fix this problem, or to make ft_sourceparcellate work so that I can avoid this issue altogether? I realize there is a lot of code and steps to go through here, and I appreciate any help that you can provide. This mailing list has been a great resource for me so far, and I hope I can return the favor to some fellow fieldtrippers soon!

Best,
Michael Glassen








________________________________
From: fieldtrip <fieldtrip-bounces at science.ru.nl> on behalf of Schoffelen, J.M. (Jan Mathijs) <jan.schoffelen at donders.ru.nl>
Sent: Thursday, May 14, 2020 2:02:38 AM
To: FieldTrip discussion list
Subject: Re: [FieldTrip] Computing source-level ROI EEG connectivity

Dear Michael,

The convention is left column to right column, so in this case RGAST to LGAST. the stuff between the ‘[‘ is to indicate that the pairwise metric has been obtained with a bivariate model that included LGAST and RGAST.

Best wishes,
Jan-Mathijs



On 14 May 2020, at 03:14, Michael Glassen - Biomedical Engineer <MGlassen at kesslerfoundation.org<mailto:MGlassen at kesslerfoundation.org>> wrote:

Hi Jan-Mathijs,

I implemented all of your suggestions but I had a quick question on the organization of the bivariate granger connectivity structure. From what I can understand, this should compute connectivity from each channel to every other channel. This is my connectivity structure:
struct with fields:

         labelcmb: {14762×2 cell}
           dimord: 'chancmb_freq'
    grangerspctrm: [14762×201 double]
             freq: [1×201 double]
              cfg: [1×1 struct]

And looking at an entry of labelcmb(14762,:) gives:
1×2 cell array

    {'RGAST[RGAST,LGAST]'}    {'LGAST[RGAST,LGAST]'}

So does this mean that grangerspctrm(14762,:) is the granger connectivity from RGAST to LGAST, or is it from LGAST to RGAST?


Best,
Michael Glassen


________________________________
From: fieldtrip <fieldtrip-bounces at science.ru.nl<mailto:fieldtrip-bounces at science.ru.nl>> on behalf of Schoffelen, J.M. (Jan Mathijs) <jan.schoffelen at donders.ru.nl<mailto:jan.schoffelen at donders.ru.nl>>
Sent: Saturday, May 9, 2020 4:11:51 AM
To: FieldTrip discussion list
Subject: Re: [FieldTrip] Computing source-level ROI EEG connectivity

Hi Wanze,

I have two short questions regarding your suggestions to Michael: 1) any reason for why doing svd for concatenated trials would work better than doing svd per trial?

Well, it’s not that ‘it works better’, but using a per trial svd will result in a different orientation estimate per trial, which then inherently assumes a source model that fixes the location of the dipole, but not its orientation.


2) If we do svd with concatenated trials do we need to re-segment the data into epochs/trials?  I think ft_freqanalysis and ft_connectivity would work way more efficiently if they are applied to data in epochs rather than one long trial, although I am not 100% assured.

Indeed, in most situations (particularly if the experimental protocol is epoch based) it is mandatory to maintain the epoch-structure in the spectral decomposition and connectivity estimation step. To this end the concatenation step is an intermediate one, used only to compute the principal orientation, and this orientation can then be applied to the data structure that still contains the epochs, either using a for-loop across the individual epochs, or exploiting the external/cellfunction code, by a direct multiplication.

Best wishes,
Jan-Mathijs




Look forward to hearing from you.

Best,
Wanze



Schoffelen, J.M. (Jan Mathijs) <jan.schoffelen at donders.ru.nl<mailto:jan.schoffelen at donders.ru.nl>>于2020年5月8日 周五上午10:35写道:
Hi Michael,

The WFU atlas is a volumetrically defined atlas, correct? So, in order for the parcellation to work, the atlas needs to be mapped onto the cortically constrained sheet of dipoles that you used for source reconstruction.
This is not entirely trivial, but doable. If you obtained your atlas already as represented on a cortically constrained set of locations, then you need to ensure that these locations topologically map to the dipole locations of your source model.

Best wishes,
Jan-Mathijs






On 8 May 2020, at 16:15, Michael Glassen - Biomedical Engineer <MGlassen at kesslerfoundation.org<mailto:MGlassen at kesslerfoundation.org>> wrote:

Thank you everyone for the help!

Just one final question, I got the Brodmann area atlas and the hemisphere atlas from this toolbox SPM “WFU-PickAtlas”.

Does anyone know if there is a Brodmann area atlas that is known to work with fieldtrip?

Best,
Michael Glassen
________________________________
From: fieldtrip <fieldtrip-bounces at science.ru.nl<mailto:fieldtrip-bounces at science.ru.nl>> on behalf of Schoffelen, J.M. (Jan Mathijs) <jan.schoffelen at donders.ru.nl<mailto:jan.schoffelen at donders.ru.nl>>
Sent: Friday, May 8, 2020 3:17:56 AM
To: FieldTrip discussion list
Subject: Re: [FieldTrip] Computing source-level ROI EEG connectivity

Hi Michael,

Adding to Wanze’s and Xavier’s replies:


               I went through and plotted a couple different time series from the mom field from different trials and the same dipole, they are not equal and the shape looks ok to me, the magnitudes range from 10^-7 to 10^-4, is that normal? Source was calculated with everything in meters if that matters. Can you explain a little more the other method for calculating mom? The only option I have to add is keepfilter to sourceanalysis? And then I multiply each of these filters against the trial data matrix and it will leave me with what rawtrial is giving me but more reliably? Can I then still use the parcellate option on the output here? Or is this not necessary as rawtrial seems to be working?

If ‘rawtrial’ works, than this would be fine. I was a bit worried (and I’d need to look in the code myself in some more detail) because historically the ‘rawtrial’-functionality was implemented for the beamformer algorithms, and not for the MNE. As such, it has never been tested thoroughly for MNE, so although it seems to produce an output for you, I am not 100% sure that it’s correct. Also, anecdotally, the ‘rawtrial’/’singletrial’ functionality for the beamformers is shaky in itself, and we still have it on our to-do-list to make the code more internally consistent and correct.



-In order to get a ‘parcellated’ representation of your source level data, I recommend to use ft_sourceparcellate, which should achieve the parcellation you performed by hand (with the binary masks).

               I tried using the ft_sourceparcellate function but I was running into a memory issue when ran on the rawtrial source, and when I remove that option and run it on the default trial-averaged sources I get a different error, which is pasted below. mri2 is the broddman area atlas interpolated onto my source model. Also, is there any way to parcellate twice with the function like I am doing by hand? The broddman area atlas I have is not divided into left and right hemispheres so I have another atlas I am using that is divided into hemispheres.

mri2 =

                                             struct with fields:

                                             pos: [8196×3 double]
                                              tri: [16384×3 double]
                                             unit: 'm'
                                              tissue: [8196×1 double]
                                             tissuelabel: {1×70 cell}
                                             cfg: [1×1 struct]

                              cfg =

                                             struct with fields:

                                              method: 'mean'
                                             parcellation: 'tissue'
                                               parameter: 'mom'

ft_sourceparcellate(cfg,sourceEEG,mri2)
there is no "Dentate" in "tissue"
there is no "Hypothalamus" in "tissue"
there is no "Substania Nigra" in "tissue"
there is no "Caudate Tail" in "tissue"
there is no "Putamen" in "tissue"
there is no "Medial Geniculum Body" in "tissue"
there is no "Lateral Globus Pallidus" in "tissue"
there is no "Subthalamic Nucleus" in "tissue"
there is no "Medial Globus Pallidus" in "tissue"
there is no "Lateral Geniculum Body" in "tissue"
there is no "Anterior Commissure" in "tissue"
there is no "Ventral Posterior Lateral Nucleus" in "tissue"
there is no "Ventral Posterior Medial Nucleus" in "tissue"
there is no "Ventral Lateral Nucleus" in "tissue"
there is no "Ventral Anterior Nucleus" in "tissue"
there is no "Caudate Body" in "tissue"
there is no "Lateral Posterior Nucleus" in "tissue"
there is no "Lateral Dorsal Nucleus" in "tissue"
there is no "Midline Nucleus" in "tissue"
there are in total 8196 positions, 8190 positions are inside the brain, 2611 positions have a label
2609 of the positions inside the brain have a label
2609 of the labeled positions are inside the brain
5581 of the positions inside the brain do not have a label
creating 70 parcels for parameter mom by taking the mean
computing parcellation
Index exceeds the number of array elements (0).

Error in ft_sourceparcellate>cellmean1 (line 488)
y = x{1};

Error in ft_sourceparcellate (line 226)
                                             tmp(j,:,:) = cellmean1(dat(seg==j));

               -If I cannot get source parcellate to work, does my method of creating a mask and pulling out the source powers seem sound? I see that I should use mom instead of pow, so would I then just take all the voxels in an ROI and average the x direction together, the y direction together and the z together? Then find the direction with the largest power and use that as my 1by x time series power(using svd as described in documentation)? This is what I’m assuming sourceparcellate does if I select ‘eig’ as the method.

-With regard to thte parcellation: understood if memory limitations prevent you from using it. Yet, the error message you get when using the trial-averaged data suggests an issue with your atlas. Where did you get it from? It looks as if only 2611 out of the 8196 cortical locations have a label. Is that according to your expectations?
-If indeed you are to manually parcellate (with the mask), you can do one of the following:
  * average x/y/z across dipole locations, followed by an svd (I’d recommend to do this svd on the trial-concatenated data, rather than on a per-trial basis)
  * (to mimick the ‘eig’ method): concatenate the time series across dipole locations (and trials), followed by a single svd on the whole matrix.





ALSO IMPORTANT: if you go for a granger-like measure (granger/dtf/pdc), in the spectral analysis you should probably use a decent amount of zero padding (cfg.pad) and sufficient tapsmofrq in order to get spectral data that stands a chance to get a stable factorization.


               -Finally, if I choose to go for granger, what kind of cfg.pad and tapsmofrq would you recommend? I am mostly interested in the beta band(12-30Hz) and my trial lengths are 357 samples(1.3 seconds)

Irrespective of using granger or the disrecommended dtf/pdc, all techniques that require a spectral factorization need padding and smoothing. In your case I’d recommend a padding of 4 (cfg.pad = 4), and a cfg.tapsmofrq somewhere in the range of 3 and 6 (but the latter is a bit of ‘wet finger work’, as we sould say in Dutch -> google for ’natte vingerwerk’ if you want to learn more).

Best wishes,
Jan-Mathijs



Subject: Re: [FieldTrip] Computing source-level ROI EEG connectivity

Dear Michael,

It looks that you already got quite far! I agree that the DTF values do not make much sense.

a few pointers:

-I would use some regularisation for  the MNE reconstruction.
-I would check whether the individual trials after source reconstruction yield meaningful values in the ‘mom’-field. Particularly, are the different trials different? Do the time series look meaningful to begin with (both in terms of signal magnitude and ’shape’)? The reason I ask, is that I am not fully sure whether the ‘rawtrial’ option is behaving according to your expectations.
-Another way for getting the mom would be to use cfg.keepfilter in ft_sourceanalysis, and left-multiply each dipole location’s ’spatial filter’ (in sourceEEG.avg.filter) with a dataEEG.trial matrix.
-IMPORTANT: don’t use the ‘pow’ for the subsequent spectral analysis and connectivity estimation. You should work with the mom.
-In order to get a ‘parcellated’ representation of your source level data, I recommend to use ft_sourceparcellate, which should achieve the parcellation you performed by hand (with the binary masks).
-The parcellation operation can be an average of the mom across the dipoles that belong to a parcel, but an alternative might be the the 1st principal component. The advantage of the latter would be that the per-parcel signal will be reduced to a single time series (rather then 3 x/y/z orientation time courses), which is convenient (and required) for the interpretation of the subsequent computations.
-You may want to think a bit about the depth weighting, since the MNE favors superficial locations in terms of amplitude, and this will also percolate to the way the parcellated time series are computed.
-IMPORTANT: using ‘dtf’ without any additional specification of parameters is probably not what you want here. The reason is that under the hood a multivariate factorization of the spectral matrix is performed (at least: an attempt is made), which will miserably fail in your case. The reason for this failure (thinking a bit more about this, I am quite surprised that it worked to begin with) is the fact that you ask for a factorization of a size NparcelxNparcel(xfrequency bins) matrix, where the effective rank of the matrix is less than the number of channels in your EEG array. Even if Nparcel is less than the number of EEG electrodes, this factorization will be very poorly estimated and nonsensical. I’d recommend using ‘granger’ as a method, in combination with cfg.granger.sfmethod = ‘bivariate’. This results in a parcel-pairwise factorization (which is hopefully stable most of the time). I am not a fan of dtf/pdc anyway given that the measures are claimed to provide estimates unproblematic estimates that are interpretable without any caveats.
-ALSO IMPORTANT: if you go for a granger-like measure (granger/dtf/pdc), in the spectral analysis you should probably use a decent amount of zero padding (cfg.pad) and sufficient tapsmofrq in order to get spectral data that stands a chance to get a stable factorization.

So, no definitive answers unfortunately, but perhaps some food for thought that might get you closer to a good result.

Good luck and keep up the good work,

Jan-Mathijs



J.M.Schoffelen, MD PhD
Associate PI, VIDI-fellow - PI, language in interaction
Telephone: +31-24-3614793
Physical location: room 00.028
Donders Centre for Cognitive Neuroimaging, Nijmegen, The Netherlands


On 6 May 2020, at 22:34, Michael Glassen - Biomedical Engineer <MGlassen at kesslerfoundation.org<mailto:MGlassen at kesslerfoundation.org>> wrote:

Hi all,

I am having issues using fieldtrip for source reconstruction and connectivity analysis with preprocessed EEG data. I believe the issue is occurring when I try to project my sources to specific ROIs before getting the connectivity. I have outlined my pipeline below:

-Because I do not have subject MRIs, I am using a template headmodel and sourcemodel that come included with fieldtrip
               - headmodel = standard_bem.mat
               -sourcemodel = cortex_8196.surf.gii

-After importing EEG data, I project the EEG electrodes to the scalp using the code below:
               if strcmp(headmodel.type, 'bemcp')
             scalp_index = 3;
elseif strcmp(headmodel.type, 'dipoli')
             scalp_index = 1;
end
cfg = [];
cfg.method = 'project';
cfg.headshape = headmodel.bnd(scalp_index);

eegData.elec = ft_electroderealign(cfg, eegData.elec);


-I then calculate covariance on a trial basis and calculate the leadfield matrix:

               cfg = [];
cfg.covariance = 'yes';
cfg.covariancewindow = [-inf 0];
cfg.keeptrials = 'yes';
tlckEeg = ft_timelockanalysis(cfg, eegData);
cfg         = [];
cfg.elec    = tlckEeg.elec;   % sensor information
cfg.channel = tlckEeg.label;  % the used channels
cfg.grid    = sourcemodel;   % source points
cfg.headmodel = headmodel;   % volume conduction model
cfg.singleshell.batchsize = 5000; % speeds up the computation
leadfield   = ft_prepare_leadfield(cfg);


-Now with the leadfield calculated, I calculate sources on a trial basis here:

cfg               = [];
cfg.method        = 'mne';
cfg.sourcemodel   = leadfield;
cfg.headmodel     = headmodel;
cfg.mne.prewhiten = 'yes';
cfg.mne.lambda    = 0;
cfg.mne.scalesourcecov = 'yes';
cfg.rawtrial      = 'yes';
sourceEEG          = ft_sourceanalysis(cfg,tlckEeg);
               sourceEEG =

                              struct with fields:

                              time: [1×357 double]
                              inside: [8196×1 logical]
                              pos: [8196×3 double]
                               tri: [16384×3 double]
                              method: 'rawtrial'
                              trial: [1×400 struct]
                              df: 400
                              cfg: [1×1 struct]


               sourceEEG.trial =

                              1×400 struct array with fields:

                              mom
                               pow
                              noisecov

size(sourceEEG.trial(1).pow)

ans =

                                             8196         357

-So at this point I have 400 trials, and for each of these trials I have a time course of source power at 8196 voxels and 357 time points for each of those voxels.
-I then import two atlases, one that includes broddman areas and one that includes left and right hemispheres. I interpolate these atlases onto my source model and get the following:
               broddman =
struct with fields:

                                      pos: [8196×3 double]
                                       tri: [16384×3 double]
                                        unit: 'm'
                                       tissue: [8196×1 double] (each of these is a value 1-70, corresponding to which atlas entry the voxel is located in)
                                       tissuelabel: {1×70 cell}(ROI labels for the atlas, includes broddman areas not separated by hemisphere)
                                      cfg: [1×1 struct]
hemis2 =
struct with fields:
pos: [8196×3 double]
                                      tri: [16384×3 double]
                                        unit: 'm'
                                        tissue: [8196×1 double] (each of these is a value 1-7, corresponding to which atlas entry the voxel is located in)

                                         tissuelabel: {1×7 cell} (ROI Labels for hemisphere atlas, contains 3 Left areas, 3 Right Areas and one inter-hemispheric)
                                      cfg: [1×1 struct]


Now I create a binary mask for each left and right broddman area. For each ROI in the broddman area atlas(1-70), I find all voxels that are located within. This gives me a list of voxels for each broddman area. I then take each of these lists and find which are in the left hemisphere and which are in the right to get left and right broddman areas.
After these steps I am left with 140 binary masks, (size 1x8196), which equal 1 if the voxel is contained in the specific ROI and 0 if it is not. Using these masks, I extract the time course power for each ROI by taking the mean power of every voxel in the roi at each time point:
                    roiData(i,i3,i2) = mean(sourceEEG.trial(i2).pow(Masks{i},i3));

where i = roi number,

       i2 = trial number,

       i3 = time point

In order to get this data into fieldtrip format, I put ROI data into an EEGLAB structure along with 6 emg channels that were recorded at the same time and use eeglab2fieldtrip function to have a fieldtrip structure containing the broddman area sources as channels. (contained in header)

After this I perform the final step of computing dtf connectivity between each of the source rois and the emg channels with the code below:

cfg           = [];
cfg.method    = 'mtmfft';
cfg.taper     = 'dpss';
cfg.output    = 'fourier';
cfg.tapsmofrq = 2;
freq          = ft_freqanalysis(cfg, header);
cfg           = [];
cfg.method    = 'dtf';
dtf           = ft_connectivityanalysis(cfg, freq);


The values I get in DTF are extremely low, 10^-20. I am not sure why but I believe it has to do with the way I am projecting source activations onto specific ROIs. Any help or advice on how to change my pipeline is appreciated.


Best,
Michael Glassen



  ­­   _______________________________________________
fieldtrip mailing list
https://mailman.science.ru.nl/mailman/listinfo/fieldtrip
https://doi.org/10.1371/journal.pcbi.1002202



  ­­   _______________________________________________
fieldtrip mailing list
https://mailman.science.ru.nl/mailman/listinfo/fieldtrip
https://doi.org/10.1371/journal.pcbi.1002202


  ­­
_______________________________________________
fieldtrip mailing list
https://mailman.science.ru.nl/mailman/listinfo/fieldtrip
https://doi.org/10.1371/journal.pcbi.1002202

_______________________________________________
fieldtrip mailing list
https://mailman.science.ru.nl/mailman/listinfo/fieldtrip
https://doi.org/10.1371/journal.pcbi.1002202
_______________________________________________
fieldtrip mailing list
https://mailman.science.ru.nl/mailman/listinfo/fieldtrip
https://doi.org/10.1371/journal.pcbi.1002202


  ­­   _______________________________________________
fieldtrip mailing list
https://mailman.science.ru.nl/mailman/listinfo/fieldtrip
https://doi.org/10.1371/journal.pcbi.1002202

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mailman.science.ru.nl/pipermail/fieldtrip/attachments/20200516/9e9947a7/attachment-0001.htm>


More information about the fieldtrip mailing list