% POP_FILEIO - import data files into EEGLAB using FileIO
%
% Usage:
% >> OUTEEG = pop_fileio; % pop up window
% >> OUTEEG = pop_fileio( filename );
% >> OUTEEG = pop_fileio( header, dat, evt );
%
% Inputs:
% filename - [string] file name
% header - fieldtrip data header
% data - fieldtrip raw data
% evt - fieldtrip event structure
%
% Optional inputs:
% 'channels' - [integer array] list of channel indices
% 'samples' - [min max] sample point limits for importing data
% 'trials' - [min max] trial's limit for importing data
% 'dataformat' - [string] data format. Default is automatic. Available
% choices are available in ft_read_data
% 'memorymapped' - ['on'|'off'] import memory mapped file (useful if
% encountering memory errors). Default is 'off'.
% 'makecontinuous' - ['on'|'off'] make data continuous (for data epochs
% only). This is relevant for MEG data. Default is 'off'.
%
% Outputs:
% OUTEEG - EEGLAB data structure
%
% Author: Arnaud Delorme, SCCN, INC, UCSD, 2008-
%
% Note: FILEIO toolbox must be installed.
% Copyright (C) 2008 Arnaud Delorme, SCCN, INC, UCSD, arno@salk.edu
%
% This file is part of EEGLAB, see http://www.eeglab.org
% for the documentation and details.
%
% Redistribution and use in source and binary forms, with or without
% modification, are permitted provided that the following conditions are met:
%
% 1. Redistributions of source code must retain the above copyright notice,
% this list of conditions and the following disclaimer.
%
% 2. Redistributions in binary form must reproduce the above copyright notice,
% this list of conditions and the following disclaimer in the documentation
% and/or other materials provided with the distribution.
%
% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
% ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
% SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
% INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
% CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
% ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
% THE POSSIBILITY OF SUCH DAMAGE.
function [EEG, command] = pop_fileio(filename, varargin)
EEG = [];
command = '';
if exist('plugin_askinstall')
if ~plugin_askinstall('Fileio', 'ft_read_data'), return; end
end
alldata = [];
event = [];
if nargin < 1
% ask user
ButtonName = questdlg2('Do you want to import a file or a folder?', ...
'FILE-IO import', ...
'Folder', 'File', 'File');
if strcmpi(ButtonName, 'file')
[filename, filepath] = uigetfile('*.*', 'Choose a file or header file -- pop_fileio()');
drawnow;
if filename(1) == 0
return;
end
filename = fullfile(filepath, filename);
else
filename = uigetdir('*.*', 'Choose a folder -- pop_fileio()');
drawnow;
if filename(1) == 0
return;
end
end
% open file to get infos
% ----------------------
formats = { 'auto' '4d' '4d_pdf' '4d_m4d' '4d_xyz' 'bci2000_dat' 'besa_avr' 'besa_swf' 'biosemi_bdf' 'bham_bdf' 'biosemi_old' 'biosig' ...
'gdf' 'brainvision_eeg' 'brainvision_dat' 'brainvision_seg' 'ced_son' 'deymed_ini' 'deymed_dat' 'emotiv_mat' 'gtec_mat' ...
'itab_raw' 'combined_ds' 'ctf_ds' 'ctf_meg4' 'ctf_res4' 'ctf_old' 'read_ctf_meg4' 'ctf_read_meg4' 'ctf_shm' 'dataq_wdq' ...
'eeglab_set' 'eeglab_erp' 'spmeeg_mat' 'ced_spike6mat' 'edf' 'eep_avr' 'eep_cnt' 'eyelink_asc' 'fcdc_buffer' 'fcdc_buffer_offline' ...
'fcdc_matbin' 'fcdc_mysql' 'egi_egia' 'egi_egis' 'egi_sbin' 'egi_mff_v1' 'egi_mff_v2' 'micromed_trc' 'mpi_ds' 'mpi_dap' ...
'netmeg' 'neuralynx_dma' 'neuralynx_sdma' 'neuralynx_ncs' 'neuralynx_nse' 'neuralynx_nte' 'neuralynx_ttl' 'neuralynx_tsl' ...
'neuralynx_tsh' 'neuralynx_bin' 'neuralynx_ds' 'neuralynx_cds' 'nexstim_nxe' 'ns_avg' 'ns_cnt' 'ns_cnt16' 'ns_cnt32' 'ns_eeg' ...
'neuromag_fif' 'neuromag_mne' 'neuromag_mex' 'neuroprax_eeg' 'plexon_ds' 'plexon_ddt' 'read_nex_data' 'read_plexon_nex' 'plexon_nex' ...
'plexon_plx' 'yokogawa_ave' 'yokogawa_con' 'yokogawa_raw' 'nmc_archive_k' 'neuroshare' 'bucn_nirs' 'riff_wave' 'neurosim_ds' ...
'neurosim_signals' 'neurosim_evolution' 'neurosim_spikes' 'manscan_mb2' 'manscan_mbi' 'neuroscope_bin' };
eeglab_options;
mmoval = option_memmapdata;
disp('Reading data file header...');
dat = ft_read_header(filename);
valueFormat = 1;
if strcmpi(filename(end-2:end), 'mff')
valueFormat = 48;
end
uilist = { { 'style' 'text' 'String' 'Channel list (default all):' } ...
{ 'style' 'edit' 'string' '' 'tag' 'chan' } ...
{ 'style' 'text' 'String' [ 'Data range (in sample points) (default all [1 ' int2str(dat.nSamples) '])' ] } ...
{ 'style' 'edit' 'string' '' 'tag' 'range' } ...
};
geom = { [3 1.5] [3 1.5] };
if dat.nTrials > 1
uilist{end+1} = { 'style' 'text' 'String' [ 'Trial range (default all [1 ' int2str(dat.nTrials) '])' ] };
uilist{end+1} = { 'style' 'edit' 'string' '' 'tag' 'trials' };
geom = { geom{:} [3 1.5] };
end
uilist = { uilist{:} ...
{ 'style' 'text' 'String' 'Data format' } ...
{ 'style' 'popupmenu' 'string' formats 'value' valueFormat 'listboxtop' valueFormat 'tag' 'format' } ...
{ 'style' 'checkbox' 'String' 'Import as memory mapped file (use in case of out of memory) - beta' 'value' option_memmapdata 'tag' 'memmap' } };
geom = { geom{:} [3 1.5] 1 };
if dat.nTrials > 1
uilist{end+1} = { 'style' 'checkbox' 'String' 'Convert data trials to continuous data' 'tag' 'makecontinuous' };
geom = { geom{:} 3 };
end
[result,~,~,restag] = inputgui( geom, uilist, 'pophelp(''pop_fileio'')', 'Load data using FILE-IO -- pop_fileio()');
if isempty(result)
return;
end
if ~isfield(restag, 'trials')
restag.trials = '';
end
if ~isfield(restag, 'makecontinuous')
restag.makecontinuous = 0;
end
options = {};
if ~isempty(restag.chan ), options = { options{:} 'channels' eval( [ '[' restag.chan ']' ] ) }; end
if ~isempty(restag.range ), options = { options{:} 'samples' eval( [ '[' restag.range ']' ] ) }; end
if ~isempty(restag.trials), options = { options{:} 'trials' eval( [ '[' restag.trials ']' ] ) }; end
if ~isempty(restag.format), options = { options{:} 'dataformat' formats{restag.format} }; end
if restag.memmap , options = { options{:} 'memorymapped' fastif(restag.memmap, 'on', 'off') }; end
if restag.makecontinuous, options = { options{:} 'makecontinuous' fastif(restag.makecontinuous, 'on', 'off') }; end
else
ft_defaults;
if ~isstruct(filename)
dat = ft_read_header(filename);
options = varargin;
else
dat = filename;
filename = '';
alldata = varargin{1};
options = {};
if nargin >= 3
event = varargin{2};
end
end
end
% decode input parameters
% -----------------------
ft_defaults;
g = struct(options{:});
if ~isfield(g, 'samples'), g.samples = []; end
if ~isfield(g, 'trials'), g.trials = []; end
if ~isfield(g, 'channels'), g.channels = []; end
if ~isfield(g, 'dataformat'), g.dataformat = 'auto'; end
if ~isfield(g, 'memorymapped'), g.memorymapped = 'off'; end
if ~isfield(g, 'makecontinuous'), g.makecontinuous = 'off'; end
% import data
% -----------
EEG = eeg_emptyset;
fprintf('Reading data ...\n');
dataopts = {};
% In case of FIF files convert EEG channel units to uV in FT options
[filePath, fileNameNoExt, filext] = fileparts(filename);
if strcmpi(filext,'.fif')
fprintf(2, 'FIF file detected, check if channel locations are rotated properly in the channel editor\n')
eegchanindx = find(strcmpi(dat.chantype,'eeg'));
if ~isempty(eegchanindx) && isfield (dat,'chanunit')
if ~all(strcmpi(dat.chanunit(eegchanindx),'uv'))
fprintf('Forcing EEG channel units to ''uV'' ...... \n');
chanunitval = dat.chanunit;
chanunitval(eegchanindx) = {'uV'};
dataopts = { dataopts{:} 'chanunit', chanunitval};
else
fprintf('EEG channel units already in ''uV'' \n');
end
end
end
if ~isempty(g.samples ), dataopts = { dataopts{:} 'begsample', g.samples(1), 'endsample', g.samples(2)}; end
if ~isempty(g.trials ), dataopts = { dataopts{:} 'begtrial', g.trials(1), 'endtrial', g.trials(2)}; end
if ~strcmpi(g.dataformat, 'auto'), dataopts = { dataopts{:} 'dataformat' g.dataformat }; end
if strcmpi(g.memorymapped, 'off') || ~isempty(alldata)
if ~isempty(g.channels), dataopts = { dataopts{:} 'chanindx', g.channels }; end
if isempty(alldata)
alldata = ft_read_data(filename, 'header', dat, dataopts{:});
elseif isstruct(alldata) && isfield(alldata, 'trial')
alldata = alldata.trial;
end
else
% read memory mapped file
g.datadims = [ dat.nChans dat.nSamples dat.nTrials ];
disp('Importing as memory mapped array, this may take a while...');
if isempty(g.channels), g.channels = [1:g.datadims(1)]; end
if ~isempty(g.samples ), g.datadims(2) = g.samples(2) - g.samples(1); end
if ~isempty(g.trials ), g.datadims(3) = g.trials(2) - g.trials(1); end
g.datadims(1) = length(g.channels);
alldata = mmo([], g.datadims);
for ic = 1:length(g.channels)
alldata(ic,:,:) = ft_read_data(filename, 'header', dat, dataopts{:}, 'chanindx', g.channels(ic));
end
end
% convert to seconds for sread
% ----------------------------
if isfield(dat, 'hdr') && ~isfield(dat, 'Fs')
if isfield(dat, 'fsample')
EEG.srate = dat.fsample;
else
EEG.srate = dat.hdr.Fs;
end
EEG.nbchan = dat.hdr.nChans;
EEG.data = alldata;
if iscell(EEG.data)
EEG.data = [ EEG.data{:} ];
end
EEG.setname = '';
EEG.comments = [ 'Original file: ' filename ];
EEG.xmin = -dat.hdr.nSamplesPre/EEG.srate;
dat.nTrials = dat.hdr.nTrials;
EEG.trials = dat.hdr.nTrials;
else
EEG.srate = dat.Fs;
EEG.nbchan = dat.nChans;
EEG.data = alldata;
EEG.setname = '';
EEG.comments = [ 'Original file: ' filename ];
EEG.xmin = -dat.nSamplesPre/EEG.srate;
EEG.trials = dat.nTrials;
end
if size(alldata,3) > 1
EEG.trials = size(alldata,3);
EEG.pnts = size(alldata,2);
else
if dat.nTrials == 1
EEG.pnts = size(alldata,2);
else
EEG.pnts = dat.nSamples;
end
end
if isfield(dat, 'label') && ~isempty(dat.label)
EEG.chanlocs = struct('labels', dat.label);
EEG.nbchan = length(dat.label);
% START ----------- Extracting EEG channel location
% Note: Currently for extensions where FT is able to generate valid 'labels' and 'elec' structure (e.g. FIF)
%If more formats, add them below
try
if isfield(dat,'elec') && ~isfield(dat.elec, 'chanpos')
dat.elec.chanpos = dat.elec.elecpos;
end
% fix issue with different units
if isfield(dat,'elec') && isfield(dat,'grad')
try
dat.elec = checkunit(dat.elec); % sometimes the unit is wrong
dat.grad = checkunit(dat.grad);
if isequal(dat.elec.unit, 'cm') && isequal(dat.grad.unit, 'mm')
dat.elec.chanpos = dat.elec.chanpos*10;
dat.elec.unit = 'mm';
end
catch, end
end
if isfield(dat,'elec')
if isfield(dat, 'label')
for iChan = 1:length(dat.label)
indLabel = strmatch(dat.label{iChan}, dat.elec.label, 'exact');
EEG.chanlocs(iChan).labels = dat.label{iChan};
if ~isempty(indLabel) && ~isnan(dat.elec.chanpos(indLabel,1))
EEG.chanlocs(iChan).labels = dat.elec.label{indLabel};
EEG.chanlocs(iChan).X = dat.elec.chanpos(indLabel,1);
EEG.chanlocs(iChan).Y = dat.elec.chanpos(indLabel,2);
EEG.chanlocs(iChan).Z = dat.elec.chanpos(indLabel,3);
if isfield(dat, 'chantype')
EEG.chanlocs(iChan).type = dat.chantype{iChan};
elseif isfield(dat.elec, 'chantype')
EEG.chanlocs(iChan).type = dat.elec.chantype{iChan};
end
end
end
end
end
if isfield(dat, 'label')
for iChan = 1:length(dat.label)
if isfield(dat, 'chantype')
EEG.chanlocs(iChan).type = dat.chantype{iChan};
elseif isfield(dat,'elec') && isfield(dat.elec, 'chantype')
EEG.chanlocs(iChan).type = dat.elec.chantype{iChan};
end
end
end
if isfield(dat.elec, 'unit')
EEG.chaninfo.unit = dat.elec.unit;
end
catch
fprintf('pop_fileio: Unable to import EEG channel location\n');
end
try
if isfield(dat,'grad')
if isfield(dat, 'label')
for iChan = 1:length(dat.label)
indLabel = strmatch(dat.label{iChan}, dat.grad.label, 'exact');
EEG.chanlocs(iChan).labels = dat.label{iChan};
if ~isempty(indLabel) && ~isnan(dat.grad.chanpos(indLabel,1))
EEG.chanlocs(iChan).labels = dat.grad.label{indLabel};
EEG.chanlocs(iChan).X = dat.grad.chanpos(indLabel,1);
EEG.chanlocs(iChan).Y = dat.grad.chanpos(indLabel,2);
EEG.chanlocs(iChan).Z = dat.grad.chanpos(indLabel,3);
if isfield(dat, 'chantype')
EEG.chanlocs(iChan).type = dat.chantype{iChan};
end
end
end
end
end
if isfield(dat.grad, 'unit')
EEG.chaninfo.unit = dat.grad.unit;
end
if isfield(dat.grad, 'coordsys') && contains(lower(dat.grad.coordsys), 'neuromag')
EEG.chaninfo.nosedir = '+Y';
end
catch
fprintf('pop_fileio: Unable to import EEG channel location\n');
end
EEG.etc.fileio_dat = dat;
EEG.chanlocs = convertlocs(EEG.chanlocs,'cart2all');
EEG.urchanlocs = EEG.chanlocs;
% END ----------- Extracting EEG channel location
end
% import fiducial in associated coordsystem file if present
% ---------------------------------------------------------
if ~isempty(fileNameNoExt) && length(fileNameNoExt) > 3
coordSystemFile = dir(fullfile(filePath, [ fileNameNoExt(1:4) '*coordsystem.json' ]));
if length(coordSystemFile) == 1
coordSystemFileName = fullfile(coordSystemFile(1).folder, coordSystemFile(1).name);
if ~exist('bids_importcoordsystemfile', 'file')
fprintf(2, 'BIDS coordsystem file %s detected but not imported, install bids-matlab-tools to import it\n', coordSystemFileName);
else
fprintf('BIDS coordsystem file detected %s and imported (may contain fiducials)\n', coordSystemFileName);
EEG = bids_importcoordsystemfile(EEG, coordSystemFileName); % require the bids-matlab-tools plugin
end
elseif length(coordSystemFile) > 1
fprintf(2, 'More than one BIDS coordsystem file detected so none imported (may contain fiducials)\n');
end
end
% extract events
% --------------
disp('Reading events...');
if isempty(event)
try
if isequal(lower(filext), '.ncs') % special for neuralinx files
eventfile = [ strrep(fileNameNoExt, 'Pz', 'Events') '.nev'];
if exist(eventfile, 'file')
event = ft_read_event(eventfile, dataopts{:});
for i=1:length(event)
% the first sample in the datafile is 1
event(i).sample = (event(i).timestamp-double(dat.FirstTimeStamp))./dat.TimeStampPerSample + 1;
end
end
else
event = ft_read_event(filename, dataopts{:});
end
catch
disp(lasterr);
event = [];
end
end
if ~isempty(event)
subsample = 0;
if ~isempty(g.samples), subsample = g.samples(1); end
EEG.event = event;
for index = 1:length(event)
offset = fastif(isempty(event(index).offset), 0, event(index).offset);
EEG.event(index).type = event(index).value;
EEG.event(index).value = event(index).type;
EEG.event(index).latency = event(index).sample+offset+subsample;
EEG.event(index).duration = event(index).duration;
if EEG.trials > 1
EEG.event(index).epoch = ceil(EEG.event(index).latency/EEG.pnts);
end
end
EEG.event = rmfield(EEG.event, 'sample');
EEG.event = rmfield(EEG.event, 'value');
EEG.event = rmfield(EEG.event, 'offset');
if exist('eeg_checkset')
EEG = eeg_checkset(EEG, 'eventconsistency');
end
else
disp('Warning: no event found. Events might be embedded in a data channel.');
disp(' To extract events, use menu File > Import Event Info > From data channel');
end
% convert data to single precision if necessary
% ---------------------------------------------
if exist('eeg_checkset')
EEG = eeg_checkset(EEG,'makeur'); % Make EEG.urevent field
end
% convert data to continuous
% --------------------------
if strcmpi(g.makecontinuous, 'on') && EEG.trials > 1
EEG = eeg_epoch2continuous(EEG);
end
% convert data to single precision if necessary
% ---------------------------------------------
if exist('eeg_checkset')
EEG = eeg_checkset(EEG,'makeur'); % Make EEG.urevent field
end
% history
% -------
if ischar(filename)
if isempty(options)
command = sprintf('EEG = pop_fileio(''%s'');', filename);
else
command = sprintf('EEG = pop_fileio(''%s'', %s);', filename, vararg2str(options));
end
end
% check units (sometimes they are wrong)
% --------------------------------------
function sens = checkunit(sens)
if isfield(sens, 'unit')
medX = median(abs(sens.chanpos(:,1)));
if medX < 15 && isequal(sens.unit, 'mm')
sens.unit = 'cm';
end
if medX > 15 && isequal(sens.unit, 'cm')
sens.unit = 'mm';
end
end