FT Megplanar.m
FT Megplanar.m
% FT_MEGPLANAR computes planar MEG gradients gradients for raw data or average
% event-related field data. It can also convert frequency-domain data that was
computed
% using FT_FREQANALYSIS, as long as it contains the complex-valued fourierspcrm and
not
% only the powspctrm.
%
% Use as
% [interp] = ft_megplanar(cfg, data)
% where the input data corresponds to the output from FT_PREPROCESSING,
% FT_TIMELOCKANALYSIS or FT_FREQANALYSIS (with output='fourier').
%
% The configuration should contain
% cfg.planarmethod = string, can be 'sincos', 'orig', 'fitplane',
'sourceproject' (default = 'sincos')
% cfg.channel = Nx1 cell-array with selection of channels (default =
{'megmag', 'meggrad'}), see FT_CHANNELSELECTION for details
% cfg.trials = 'all' or a selection given as a 1xN vector (default =
'all')
%
% The methods orig, sincos and fitplane are all based on a neighbourhood
interpolation.
% For these methods you need to specify
% cfg.neighbours = neighbourhood structure, see FT_PREPARE_NEIGHBOURS
%
% In the 'sourceproject' method a minumum current estimate is done using a large
number
% of dipoles that are placed in the upper layer of the brain surface, followed by a
% forward computation towards a planar gradiometer array. This requires the
% specification of a volume conduction model of the head and of a source model. The
% 'sourceproject' method is not supported for frequency domain data.
%
% A dipole layer representing the brain surface must be specified with
% cfg.inwardshift = depth of the source layer relative to the head model
surface ,
% (default = 2.5 cm, which is appropriate for a skin-based head
model)
% cfg.spheremesh = number of dipoles in the source layer (default = 642)
% cfg.tolerance = tolerance ratio for leadfield matrix inverse based on a
truncated svd,
% reflects the relative magnitude of the largest singular value
% to retain (default = 1e-3)
% cfg.headshape = a filename containing headshape, a structure containing a
% single triangulated boundary, or a Nx3 matrix with surface
% points
% If no headshape is specified, the dipole layer will be based on the inner
compartment
% of the volume conduction model.
%
% Optionally, you can modify the leadfields by reducing the rank, i.e. remove the
weakest orientation
% cfg.reducerank = 'no', or number (default = 3 for EEG, 2 for MEG)
% cfg.backproject = 'yes' or 'no', determines when reducerank is applied
whether the
% lower rank leadfield is projected back onto the original
linear
% subspace, or not (default = 'yes')
%
% The volume conduction model of the head should be specified as
% cfg.headmodel = structure with volume conduction model, see
FT_PREPARE_HEADMODEL
%
% The following cfg fields are optional:
% cfg.feedback
%
% To facilitate data-handling and distributed computing you can use
% cfg.inputfile = ...
% cfg.outputfile = ...
% If you specify one of these (or both) the input data will be read from a *.mat
% file on disk and/or the output data will be written to a *.mat file. These mat
% files should contain only a single variable, corresponding with the
% input/output structure.
%
% See also FT_COMBINEPLANAR, FT_PREPARE_NEIGHBOURS
% store the original input representation of the data, this is used later on to
convert it back
isfreq = ft_datatype(data, 'freq');
istlck = ft_datatype(data, 'timelock'); % this will be temporary converted into
raw
% check if the input data is valid for this function, this converts the data if
needed
data = ft_checkdata(data, 'datatype', {'raw' 'freq'}, 'feedback', 'yes',
'hassampleinfo', 'yes', 'ismeg', 'yes', 'senstype', {'ctf64', 'ctf151', 'ctf275',
'bti148', 'bti248', 'itab153', 'yokogawa64', 'yokogawa160', 'yokogawa208'});
if isfreq
if ~isfield(data, 'fourierspctrm'), ft_error('freq data should contain Fourier
spectra'); end
end
if ~strcmp(cfg.planarmethod, 'sourceproject')
% this is limited to reading neighbours from disk and/or selecting channels
% the user should call FT_PREPARE_NEIGHBOURS directly for the actual construction
tmpcfg = keepfields(cfg, {'neighbours', 'channel', 'showcallinfo',
'trackcallinfo', 'trackusage', 'trackdatainfo', 'trackmeminfo', 'tracktimeinfo',
'checksize'});
cfg.neighbours = ft_prepare_neighbours(tmpcfg);
end
% put the low-level options pertaining to the dipole grid in their own field
cfg = ft_checkconfig(cfg, 'renamed', {'tightgrid', 'tight'}); % this is moved to
cfg.sourcemodel.tight by the subsequent createsubcfg
cfg = ft_checkconfig(cfg, 'renamed', {'sourceunits', 'unit'}); % this is moved to
cfg.sourcemodel.unit by the subsequent createsubcfg
% put the low-level options pertaining to the sourcemodel in their own field
cfg = ft_checkconfig(cfg, 'createsubcfg', {'sourcemodel'});
% move some fields from cfg.sourcemodel back to the top-level configuration
cfg = ft_checkconfig(cfg, 'createtopcfg', {'sourcemodel'});
if isfreq
ft_error('the method ''sourceproject'' is not supported for frequency data as
input');
end
% PREPARE_HEADMODEL will match the data labels, the gradiometer labels and the
% volume model labels (in case of a localspheres model) and result in a
gradiometer
% definition that only contains the gradiometers that are present in the
% data. This should exclude the non-MEG channels, so the user-defined
% cfg.channel should be overruled
tmpcfg = cfg;
tmpcfg.channel = ft_channelselection('MEG', cfg.channel);
[headmodel, axial.grad, tmpcfg] = prepare_headmodel(tmpcfg, data);
planarmontage = [];
planarmontage.tra = transform;
planarmontage.labelold = axial.grad.label;
planarmontage.labelnew = planar.grad.label;
sens = ft_determine_units(data.grad);
chanposnans = any(isnan(sens.chanpos(:))) || any(isnan(sens.chanori(:)));
if chanposnans
if isfield(sens, 'chanposold')
% temporarily replace chanpos and chanorig with the original values
sens.chanpos = sens.chanposold;
sens.chanori = sens.chanoriold;
sens.label = sens.labelold;
sens = rmfield(sens, {'chanposold', 'chanoriold', 'labelold'});
else
ft_error('The channel positions (and/or orientations) contain NaNs; this
prohibits correct behavior of the function. Please replace the input channel
definition with one that contains valid channel positions');
end
end
% the channels must be in the neighbour definition, in the grad structure, and in
the data
cfg.channel = ft_channelselection(cfg.channel, {cfg.neighbours.label});
cfg.channel = ft_channelselection(cfg.channel, sens.label);
cfg.channel = ft_channelselection(cfg.channel, data.label);
Ngrad = length(sens.label);
distance = zeros(Ngrad,Ngrad);
for i=1:size(cfg.neighbsel,1)
j=find(cfg.neighbsel(i, :));
d = sqrt(sum((sens.chanpos(j,:) - repmat(sens.chanpos(i, :), numel(j), 1)).^2,
2));
distance(i,j) = d;
distance(j,i) = d;
end
switch cfg.planarmethod
case 'sincos'
planarmontage = megplanar_sincos(cfg, sens);
case 'orig'
% method specific info that is needed
cfg.distance = distance;
planarmontage = megplanar_orig(cfg, sens);
case 'fitplane'
planarmontage = megplanar_fitplane(cfg, sens);
otherwise
% This does not work when running in deployed mode because the
% private functions that compute the planar montage are not recognized as
% such and won't be compiled, unless explicitly specified.
fun = ['megplanar_' cfg.planarmethod];
if ~exist(fun, 'file')
ft_error('unknown method for computation of planar gradient');
end
planarmontage = eval([fun '(cfg, data.grad)']);
end
% apply the linear transformation to the data
interp = ft_apply_montage(data, planarmontage, 'keepunused', 'yes', 'feedback',
cfg.feedback);
% if the original chanpos contained nans, make sure to put nans in the
% updated one as well, and move the updated chanpos values to chanposold
if chanposnans
interp.grad.chanposold = sens.chanpos;
interp.grad.chanoriold = sens.chanori;
interp.grad.labelold = sens.label;
interp.grad.chanpos = nan(size(interp.grad.chanpos));
interp.grad.chanori = nan(size(interp.grad.chanori));
end
end
if istlck
% convert the raw structure back into a timelock structure
interp = ft_checkdata(interp, 'datatype', 'timelock');
end