function [output,frequency_parameters,notes,spike_triggers,varargout] = read_Intan(fn,varargin)
% Adapted function to read intan files. Adapted from
% 'read_Intan_RHD2000file.m' as can be downloaded from the manufacuter's
% website: http://www.intantech.com/downloads.html

% This function requires user inputs, opens no dialogues and generates no
% command line output (except errors).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% INPUTS:
% fn : Filename (including path if necessary)
% 'range' : declares that the following input is a range of data (in sec)
%           to load.
% 'ampch' : Lists the amplifier channnels to load
% 'auxch' : Lists the auxilary channels to load
% 'volt'  : Declares whether the voltage channel should be loaded or no
%           ('yes' or 'no')
% 'ADCch' : Lists the onboard ADC channels to load
% 'DIGch' : Lists the onboard digital channels to load
% 'tempch': Declares whether the temperature data should be loaded ('yes'
%           or 'no')
% 'notch' : Assign '50Hz' or '60Hz' to enable notch filtering. If left
%           undefined then no filter is applied.
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% OUTPUTS
% The first output is always the overview of all outputs.
% The second output is always a structure with frequency settings
% The third output is always the notes structure
% The fourth output is always the spike_triggers structure
% Then the function will provide the requested output as data (in
% timeseries format) and channel information pairs. This is always in a fixed
% order:
% 1. amplifier channels
% 2. auxiliary channels
% 3. ADC channels
% 4. Digital channels
% 5. volts
% 6. temperature
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% EXAMPLES:
% [output,freqs,notes,spike_triggers,ampl_data,ampl_ch,aux_data,aux_ch]...
%    = read_Intan(fn,'auxch',[1 2 3],'ampch',[1]);
% This loads the three auxiliary streams, and channel 1 from the general
% amplifier. No range is specified, so the whole stream is loaded.
%
% [output

% [output,freqs,notes,ampl_data,ampl_channels,ADC_data,...
%   ADC_channels,temp_data,temp_ch] = read_Intan_new(fn,'ampch',...
%   [1 2 4 10:20],'tempch','yes','ADCch',[1 2],'range',[1 100.1]);
%
%

% First we parse the variable inputs to check whether the list makes sense:
    p = inputParser;
    p.addRequired('Filename',@(fn) ischar(fn));
    p.addParamValue('notch','notchfreq',@(notchfreq)...
        strcmp(notchfreq,'50Hz') || strcmp(notchfreq,'60Hz'));
    p.addParamValue('range','rng',@(rng) length(rng)==2);
    p.addParamValue('ampch','chlist',@(chlist) ~isempty(chlist));
    p.addParamValue('auxch','aux',@(aux) ~isempty(aux));
    p.addParamValue('volt','Vch', @(Vch) ...
        strcmpi(Vch,'yes') || strcmpi(Vch,'no'));
    p.addParamValue('ADCch','ADC',@(ADC) ~isempty(ADC));
    p.addParamValue('DIGch','DIG',@(DIG) ~isempty(DIG)>0);
    p.addParamValue('tempch','temp',@(temp)...
        strcmpi(temp,'yes') || strcmpi(temp,'yes'));

    p.parse(fn,varargin{:,:})

% Now we assign the values defined in the inputs to the respective
% variables and set some defaults.
nrstreamsrequested = 0;
notchfreq = 0;
channellist = 0;
range = 0;
aux = 0;
supplyvolt = 0;
ADCchannels = 0;
digitalchannels = 0;
temperature = 0;
for arg=1:2:length(varargin)
    switch (varargin{arg})
        case 'notch'
            notchfreq = varargin{arg+1};
        case 'ampch'
            channellist = varargin{arg+1};
            nrstreamsrequested=nrstreamsrequested+1;
        case 'range'
            range = varargin{arg+1};
        case 'auxch'
            aux = varargin{arg+1};
            nrstreamsrequested=nrstreamsrequested+1;
        case 'volt'
            supplyvolt = 1;
            nrstreamsrequested=nrstreamsrequested+1;
        case 'ADCch'
            ADCchannels = varargin{arg+1};
            nrstreamsrequested=nrstreamsrequested+1;
        case 'DIGch'
            digitalchannels = varargin{arg+1};
            nrstreamsrequested=nrstreamsrequested+1;
        case 'tempch'
            temperature = 1;
            nrstreamsrequested=nrstreamsrequested+1;
    end
end

% The actual start of the file opening. Here we first open the file to gain
% access to the parts in it.
    fid = fopen(fn, 'r');
    s = dir(fn);
    filesize = s.bytes;

    % Check 'magic number' at beginning of file to make sure this is an Intan
    % Technologies RHD2000 data file.
    magic_number = fread(fid, 1, 'uint32');
    if magic_number ~= hex2dec('c6912702')
        error('Unrecognized file type.');
    end

    % Read version number.
    data_file_main_version_number = fread(fid, 1, 'int16');
    data_file_secondary_version_number = fread(fid, 1, 'int16');
    % Read information of sampling rate and amplifier frequency settings.
    sample_rate = fread(fid, 1, 'single');
    dsp_enabled = fread(fid, 1, 'int16');
    actual_dsp_cutoff_frequency = fread(fid, 1, 'single');
    actual_lower_bandwidth = fread(fid, 1, 'single');
    actual_upper_bandwidth = fread(fid, 1, 'single');

    desired_dsp_cutoff_frequency = fread(fid, 1, 'single');
    desired_lower_bandwidth = fread(fid, 1, 'single');
    desired_upper_bandwidth = fread(fid, 1, 'single');

    % Enables notch filtering if requested
    notch_filter_mode = fread(fid, 1, 'int16');
    notch_filter_frequency = 0;
    if strcmp(notchfreq,'50Hz')
        notch_filter_frequency = 50;
    elseif strcmp(notchfreq,'60Hz')
        notch_filter_frequency = 60;
    end

    desired_impedance_test_frequency = fread(fid, 1, 'single');
    actual_impedance_test_frequency = fread(fid, 1, 'single');

    % Place notes in data strucure
    notes = struct( ...
    'note1', fread_QString(fid), ...
    'note2', fread_QString(fid), ...
    'note3', fread_QString(fid) );

    % If data file is from GUI v1.1 or later, see if temperature sensor data
    % was saved.
    num_temp_sensor_channels = 0;
    if ((data_file_main_version_number == 1 && data_file_secondary_version_number >= 1) ...
        || (data_file_main_version_number > 1))
        num_temp_sensor_channels = fread(fid, 1, 'int16');
    end

    % Place frequency-related information in data structure.
    frequency_parameters = struct( ...
        'amplifier_sample_rate', sample_rate, ...
        'aux_input_sample_rate', sample_rate / 4, ...
        'supply_voltage_sample_rate', sample_rate / 60, ...
        'board_adc_sample_rate', sample_rate, ...
        'board_dig_in_sample_rate', sample_rate, ...
        'desired_dsp_cutoff_frequency', desired_dsp_cutoff_frequency, ...
        'actual_dsp_cutoff_frequency', actual_dsp_cutoff_frequency, ...
        'dsp_enabled', dsp_enabled, ...
        'desired_lower_bandwidth', desired_lower_bandwidth, ...
        'actual_lower_bandwidth', actual_lower_bandwidth, ...
        'desired_upper_bandwidth', desired_upper_bandwidth, ...
        'actual_upper_bandwidth', actual_upper_bandwidth, ...
        'notch_filter_frequency', notch_filter_frequency, ...
        'desired_impedance_test_frequency', desired_impedance_test_frequency, ...
        'actual_impedance_test_frequency', actual_impedance_test_frequency );
    % Define data structure for spike trigger settings.
    spike_trigger_struct = struct( ...
        'voltage_trigger_mode', {}, ...
        'voltage_threshold', {}, ...
        'digital_trigger_channel', {}, ...
        'digital_edge_polarity', {} );

    new_trigger_channel = struct(spike_trigger_struct);
    spike_triggers = struct(spike_trigger_struct);

    % Define data structure for data channels.
    channel_struct = struct( ...
        'custom_channel_name', {}, ...
        'native_channel_name', {}, ...
        'native_order', {}, ...
        'custom_order', {}, ...
        'board_stream', {}, ...
        'chip_channel', {}, ...
        'port_name', {}, ...
        'port_prefix', {}, ...
        'port_number', {}, ...
        'electrode_impedance_magnitude', {}, ...
        'electrode_impedance_phase', {} );

    new_channel = struct(channel_struct);

    % Create structure arrays for each type of data channel.
    amplifier_channels = struct(channel_struct);
    aux_input_channels = struct(channel_struct);
    supply_voltage_channels = struct(channel_struct);
    board_adc_channels = struct(channel_struct);
    board_dig_in_channels = struct(channel_struct);

    amplifier_index = 1;
    aux_input_index = 1;
    supply_voltage_index = 1;
    board_adc_index = 1;
    board_dig_in_index = 1;

    % Read signal summary from data file header.

    number_of_signal_groups = fread(fid, 1, 'int16');

    for signal_group = 1:number_of_signal_groups
        signal_group_name = fread_QString(fid);
        signal_group_prefix = fread_QString(fid);
        signal_group_enabled = fread(fid, 1, 'int16');
        signal_group_num_channels = fread(fid, 1, 'int16');
        signal_group_num_amp_channels = fread(fid, 1, 'int16');

        if (signal_group_num_channels > 0 && signal_group_enabled > 0)
            new_channel(1).port_name = signal_group_name;
            new_channel(1).port_prefix = signal_group_prefix;
            new_channel(1).port_number = signal_group;
            for signal_channel = 1:signal_group_num_channels
                new_channel(1).custom_channel_name = fread_QString(fid);
                new_channel(1).native_channel_name = fread_QString(fid);
                new_channel(1).native_order = fread(fid, 1, 'int16');
                new_channel(1).custom_order = fread(fid, 1, 'int16');
                signal_type = fread(fid, 1, 'int16');
                channel_enabled = fread(fid, 1, 'int16');
                new_channel(1).chip_channel = fread(fid, 1, 'int16');
                new_channel(1).board_stream = fread(fid, 1, 'int16');
                new_trigger_channel(1).voltage_trigger_mode = fread(fid, 1, 'int16');
                new_trigger_channel(1).voltage_threshold = fread(fid, 1, 'int16');
                new_trigger_channel(1).digital_trigger_channel = fread(fid, 1, 'int16');
                new_trigger_channel(1).digital_edge_polarity = fread(fid, 1, 'int16');
                new_channel(1).electrode_impedance_magnitude = fread(fid, 1, 'single');
                new_channel(1).electrode_impedance_phase = fread(fid, 1, 'single');

                if (channel_enabled)
                    switch (signal_type)
                        case 0
                            amplifier_channels(amplifier_index) = new_channel;
                            spike_triggers(amplifier_index) = new_trigger_channel;
                            amplifier_index = amplifier_index + 1;
                        case 1
                            aux_input_channels(aux_input_index) = new_channel;
                            aux_input_index = aux_input_index + 1;
                        case 2
                            supply_voltage_channels(supply_voltage_index) = new_channel;
                            supply_voltage_index = supply_voltage_index + 1;
                        case 3
                            board_adc_channels(board_adc_index) = new_channel;
                            board_adc_index = board_adc_index + 1;
                        case 4
                            board_dig_in_channels(board_dig_in_index) = new_channel;
                            board_dig_in_index = board_dig_in_index + 1;
                        otherwise
                            error('Unknown channel type');
                    end
                end

            end
        end
    end

    % Summarize contents of data file.
    num_amplifier_channels = amplifier_index - 1;
    num_aux_input_channels = aux_input_index - 1;
    num_supply_voltage_channels = supply_voltage_index - 1;
    num_board_adc_channels = board_adc_index - 1;
    num_board_dig_in_channels = board_dig_in_index - 1;

    % Determine how many samples the data file contains.

    % Each data block contains 60 amplifier samples.
    bytes_per_block = 60 * 4;  % timestamp data
    bytes_per_block = bytes_per_block + 60 * 2 * num_amplifier_channels;
    % Auxiliary inputs are sampled 4x slower than amplifiers
    bytes_per_block = bytes_per_block + 15 * 2 * num_aux_input_channels;
    % Supply voltage is sampled 60x slower than amplifiers
    bytes_per_block = bytes_per_block + 1 * 2 * num_supply_voltage_channels;
    % Board analog inputs are sampled at same rate as amplifiers
    bytes_per_block = bytes_per_block + 60 * 2 * num_board_adc_channels;
    % Board digital inputs are sampled at same rate as amplifiers
    if (num_board_dig_in_channels > 0)
        bytes_per_block = bytes_per_block + 60 * 2;
    end
    % Temp sensor is sampled 60x slower than amplifiers
    if (num_temp_sensor_channels > 0)
       bytes_per_block = bytes_per_block + 1 * 2 * num_temp_sensor_channels;
    end

    % How many data blocks remain in this file?
    bytes_remaining = filesize - ftell(fid);
    num_data_blocks = bytes_remaining / bytes_per_block;

    num_amplifier_samples = 60 * num_data_blocks;
    num_aux_input_samples = 15 * num_data_blocks;
    num_supply_voltage_samples = 1 * num_data_blocks;
    num_board_adc_samples = 60 * num_data_blocks;
    num_board_dig_in_samples = 60 * num_data_blocks;

    record_time = num_amplifier_samples / sample_rate;

    % Pre-allocate memory for data.
    t_amplifier = zeros(1, num_amplifier_samples);

    amplifier_data = zeros(num_amplifier_channels, num_amplifier_samples);
    aux_input_data = zeros(num_aux_input_channels, num_aux_input_samples);
    supply_voltage_data = zeros(num_supply_voltage_channels, num_supply_voltage_samples);
    temp_sensor_data = zeros(num_temp_sensor_channels, num_supply_voltage_samples);
    board_adc_data = zeros(num_board_adc_channels, num_board_adc_samples);
    board_dig_in_data = zeros(num_board_dig_in_channels, num_board_dig_in_samples);
    board_dig_in_raw = zeros(1, num_board_dig_in_samples);
    % Read sampled data from file.

    amplifier_index = 1;
    aux_input_index = 1;
    supply_voltage_index = 1;
    board_adc_index = 1;
    board_dig_in_index = 1;

    for i=1:num_data_blocks
        t_amplifier(amplifier_index:(amplifier_index+59)) = fread(fid, 60, 'uint32');
        if (num_amplifier_channels > 0)
            amplifier_data(:, amplifier_index:(amplifier_index+59)) = fread(fid, [60, num_amplifier_channels], 'uint16')';
        end
        if (num_aux_input_channels > 0)
            aux_input_data(:, aux_input_index:(aux_input_index+14)) = fread(fid, [15, num_aux_input_channels], 'uint16')';
        end
        if (num_supply_voltage_channels > 0)
            supply_voltage_data(:, supply_voltage_index) = fread(fid, [1, num_supply_voltage_channels], 'uint16')';
        end
        if (num_temp_sensor_channels > 0)
            temp_sensor_data(:, supply_voltage_index) = fread(fid, [1, num_temp_sensor_channels], 'int16')';
        end
        if (num_board_adc_channels > 0)
            board_adc_data(:, board_adc_index:(board_adc_index+59)) = fread(fid, [60, num_board_adc_channels], 'uint16')';
        end
        if (num_board_dig_in_channels > 0)
            board_dig_in_raw(board_dig_in_index:(board_dig_in_index+59)) = fread(fid, 60, 'uint16');
        end

        amplifier_index = amplifier_index + 60;
        aux_input_index = aux_input_index + 15;
        supply_voltage_index = supply_voltage_index + 1;
        board_adc_index = board_adc_index + 60;
        board_dig_in_index = board_dig_in_index + 60;

    end

    % Make sure we have read exactly the right amount of data.
    bytes_remaining = filesize - ftell(fid);
    if (bytes_remaining ~= 0)
        %error('Error: End of file not reached.');
    end

    % Close data file.
    fclose(fid);

    % Extract digital input channels to separate variables.
    for i=1:num_board_dig_in_channels
       mask = 2^(board_dig_in_channels(i).native_order) * ones(size(board_dig_in_raw));
       board_dig_in_data(i, :) = (bitand(board_dig_in_raw, mask) > 0);
    end

    % Scale voltage levels appropriately.
    amplifier_data = 0.195 * (amplifier_data - 32768); % units = microvolts
    aux_input_data = 37.4e-6 * aux_input_data; % units = volts
    supply_voltage_data = 74.8e-6 * supply_voltage_data; % units = volts
    board_adc_data = 50.354e-6 * board_adc_data; % units = volts
    temp_sensor_data = temp_sensor_data / 100; % units = deg C

    % Check for gaps in timestamps.
    num_gaps = sum(diff(t_amplifier) ~= 1);
    if (num_gaps == 0)
    else
        fprintf(1, 'Warning: %d gaps in timestamp data found.  Time scale will not be uniform!\n', ...
            num_gaps);
    end

    % Scale time steps (units = seconds).
    t_amplifier = t_amplifier / sample_rate;
    t_aux_input = t_amplifier(1:4:end);
    t_supply_voltage = t_amplifier(1:60:end);
    t_board_adc = t_amplifier;
    t_dig_in = t_amplifier;
    t_temp_sensor = t_supply_voltage;

    % If notch filtering was requested, apply it here:
    if (notch_filter_frequency > 0)
        %disp(['Applying Notch Filter of ',notchfreq]);
        for i=1:num_amplifier_channels
            amplifier_data(i,:) = ...
                notch_filter(amplifier_data(i,:), sample_rate, notch_filter_frequency, 10);
        end
    end

    % Now that we have the whole file we select the parts we need.
    % First we select the datastreams requested, then select the data range
    % requested. With every step we check whether we do have the data in
    % the file actually. If something is requested, but not included in the
    % file, a warning is displayed in the commandline.

    if nrstreamsrequested==0    %Nothing specified, all output requested
        % Output order:
        % amplifier channels => auxiliary channels => ADC channels => Digital
        % channels => volts => temperature
        output = {...
            'ampch' [1:size(amplifier_data,1)] ...
            'auxch' [1:size(aux_input_data,1)] ...
            'ADCch' [1:size(board_adc_data,1)] ...
            'DIGch' [1:size(board_dig_in_data,1)] ...
            'volt' 'yes' ...
            'tempch' 'yes'...
            };
    else
        output = cell(1,1);
        i=1;
        if sum(cellfun(@isequal,varargin,repmat({'ampch'},size(varargin))))
            output{1,i} = 'ampch';
            output{1,i+1} = channellist;
            i=i+2;
        end
        if sum(cellfun(@isequal,varargin,repmat({'auxch'},size(varargin))))
            output{1,i} = 'auxch';
            output{1,i+1} = aux;
            i=i+2;
        end
        if sum(cellfun(@isequal,varargin,repmat({'ADCch'},size(varargin))))
            output{1,i} = 'ADCch';
            output{1,i+1} = ADCchannels;
            i=i+2;
        end
        if sum(cellfun(@isequal,varargin,repmat({'DIGch'},size(varargin))))
            output{1,i} = 'DIGch';
            output{1,i+1} = digitalchannels;
            i=i+2;
        end
        if sum(cellfun(@isequal,varargin,repmat({'volt'},size(varargin))))
            output{1,i} = 'volt';
            output{1,i+1} = supplyvolt;
            i=i+2;
        end
        if sum(cellfun(@isequal,varargin,repmat({'tempch'},size(varargin))))
            output{1,i} = 'tempch';
            output{1,i+1} = temperature;
            i=i+2;
        end
    end
    for arg=1:2:length(output)
        switch (output{arg})
            case 'ampch'
                if size(amplifier_data,1)>0
                    amplifier_channels = amplifier_channels(1,output{arg+1});
                    amplifier_data = amplifier_data(output{arg+1},:);
                    ts_amp = timeseries;
                    ts_amp.time = t_amplifier;
                    ts_amp.data = amplifier_data';
                    if range ~=0
                        ts_amp = selecttime(ts_amp,range);
                    end
                    ts_amp.DataInfo.Units = 'uV';
                    ts_amp.TimeInfo.Units = 'seconds';
                    varargout{arg} = ts_amp;
                    varargout{arg+1} = amplifier_channels;
                else
                    disp('WARNING: No amplifier channels found in file!')
                end
            case 'auxch'
                if size(aux_input_data,1)>0
                    aux_input_channels = aux_input_channels(1,output{arg+1});
                    aux_input_data = aux_input_data(output{arg+1},:);
                    ts_aux = timeseries;
                    ts_aux.time = t_aux_input;
                    ts_aux.data = aux_input_data';
                    if range~=0
                        ts_aux = selecttime(ts_aux,range);
                    end
                    ts_aux.DataInfo.Unit = 'uV';
                    ts_aux.TimeInfo.Units = 'seconds';
                    varargout{arg} = ts_aux;
                    varargout{arg+1} = aux_input_channels;
                else
                    disp('WARNING: No auxiliary inputs found in file!')
                end
            case 'ADCch'
                if size(board_adc_data,1)>0
                    board_adc_channels = board_adc_channels(1,output{arg+1});
                    board_adc_data = board_adc_data(output{arg+1},:);
                    ts_board_adc = timeseries;
                    ts_board_adc.time = t_board_adc;
                    ts_board_adc.data = board_adc_data';
                    if range ~=0
                        ts_board_adc = selecttime(ts_board_adc,range);
                    end
                    ts_board_adc.DataInfo.Unit = 'uV';
                    ts_board_adc.TimeInfo.Units = 'seconds';
                    varargout{arg} = ts_board_adc;
                    varargout{arg+1} = board_adc_channels;
                else
                    disp('WARNING: No board ADC channels found in file!');
                end
            case 'DIGch'
                if size(board_dig_in_data,1)>0
                    board_dig_in_channels = board_dig_in_channels(1,output{arg+1});
                    board_dig_in_data = board_dig_in_data(output{arg+1},:);
                    ts_dig_in = timeseries;
                    ts_dig_in.time = t_amplifier;
                    ts_dig_in.data = board_dig_in_data';
                    if range ~=0
                        ts_dig_in = selecttime(ts_dig_in,range);
                    end
                    ts_dig_in.TimeInfo.Units = 'seconds';
                    varargout{arg} = ts_dig_in;
                    varargout{arg+1} = board_dig_in_channels;
                else
                    disp('WARNING: No board digital in channels found in file!');
                end
            case 'volt'
                if size(supply_voltage_data,1)>0
                    ts_volt = timeseries;
                    ts_volt.time = t_supply_voltage;
                    ts_volt.data = supply_voltage_data';
                    if range ~=0
                        ts_volt = selecttime(ts_volt,range);
                    end
                    ts_volt.TimeInfo.Units = 'seconds';
                    varargout{arg} = ts_volt;
                    varargout{arg+1} = supply_voltage_channels;
                else
                    disp('WARNING: No supply voltage data found in file!');
                end
            case 'tempch'
                if size(temp_sensor_data,1)>0
                    ts_temp = timeseries;
                    ts_temp.time = t_temp_sensor;
                    ts_temp.data = temp_sensor_data';
                    if range ~= 0
                        ts_temp = selecttime(ts_temp,range);
                    end
                    ts_temp.TimeInfo.Units = 'seconds';
                    varargout{arg} = ts_temp;
                    varargout{arg+1} = '1';
                else
                    disp('WARNING: No temperature data found in file!');
                end
        end
    end

return

function new = selecttime(old,range)
% Enter a timeseries and a range.
% Returns a new timeseries according to range.
% Range should be a 1x2 matrix with [start end]
new = timeseries;
data = old.data(old.time>range(1,1),:);
time = old.time(old.time>range(1,1));
data = data(time<range(1,2),:);
time = time(time<range(1,2));
new.data = data;
new.time = time;

return

function a = fread_QString(fid)

% a = read_QString(fid)
%
% Read Qt style QString.  The first 32-bit unsigned number indicates
% the length of the string (in 16-bit unicode characters).  If this
% number equals 0xFFFFFFFF, the string is null.

a = '';
length = fread(fid, 1, 'uint32');
if length == hex2num('ffffffff')
    return;
end
% convert length from bytes to 16-bit unicode words
length = length / 2;

for i=1:length
    a(i) = fread(fid, 1, 'uint16');
end

return

function s = plural(n)

    % s = plural(n)
    %
    % Utility function to optionally plurailze words based on the value
    % of n.

    if (n == 1)
        s = '';
    else
        s = 's';
    end

return

function out = notch_filter(in, fSample, fNotch, Bandwidth)

    % out = notch_filter(in, fSample, fNotch, Bandwidth)
    %
    % Implements a notch filter (e.g., for 50 or 60 Hz) on vector 'in'.
    % fSample = sample rate of data (in Hz or Samples/sec)
    % fNotch = filter notch frequency (in Hz)
    % Bandwidth = notch 3-dB bandwidth (in Hz).  A bandwidth of 10 Hz is
    %   recommended for 50 or 60 Hz notch filters; narrower bandwidths lead to
    %   poor time-domain properties with an extended ringing response to
    %   transient disturbances.
    %
    % Example:  If neural data was sampled at 30 kSamples/sec
    % and you wish to implement a 60 Hz notch filter:
    %
    % out = notch_filter(in, 30000, 60, 10);

    tstep = 1/fSample;
    Fc = fNotch*tstep;

    L = length(in);

    % Calculate IIR filter parameters
    d = exp(-2*pi*(Bandwidth/2)*tstep);
    b = (1 + d*d)*cos(2*pi*Fc);
    a0 = 1;
    a1 = -b;
    a2 = d*d;
    a = (1 + d*d)/2;
    b0 = 1;
    b1 = -2*cos(2*pi*Fc);
    b2 = 1;
    out = zeros(size(in));
    out(1) = in(1);
    out(2) = in(2);
    % (If filtering a continuous data stream, change out(1) and out(2) to the
    %  previous final two values of out.)

    % Run filter
    for i=3:L
        out(i) = (a*b2*in(i-2) + a*b1*in(i-1) + a*b0*in(i) - a2*out(i-2) - a1*out(i-1))/a0;
    end
return


function [dataSubset, fid] = readHDF5Subset(inputFilePath, offset, block, varargin)
	% Gets a subset of data from an HDF5 file.
	% Biafra Ahanonu
	% started: 2013.11.10
	% based on code from MathWorks; for details, see http://www.mathworks.com/help/matlab/ref/h5d.read.html
	% inputs
		%
		% offset = cell array of [xOffset yOffset frameOffset]
		% block = cell array of [xDim yDim frames], xDim and yDim should be the same size across all cell arrays, note, the "frames" dimension SHOULD NOT have overlapping frames across cell arrays
	% options
		% datasetName = hierarchy where data is stored in HDF5 file
	% changelog
		% 2013.11.30 [17:59:14]
		% 2014.01.15 [09:59:53] cleaned up code, removed unnecessary options
		% 2017.01.18 [15:01:15] added option to deal with 3D data in 4D format with singleton 4th dimension
		% 2018.09.28 - changed so can read from multiple non-contiguous	slabs of data at the same time
		% 2019.02.13 [14:52:33] - Updated to support not closing file ID and importing an existing file ID to improve speed.
		% 2019.02.13 - Updated to support when user ask for multiple offsets at the same location in file.
		% 2019.02.13 [17:57:55] - Improved duplicate frame support, finds differences in frames, loads all unique as a single slab, then loads remaining and re-organizes to be in correct order.
		% 2019.05.03 [15:42:08] - Additional 4D support in cases where a 3D offset/block request is made.
		% 2019.10.10 [12:52:54] - Add correction for frame order. Select hyperslab in HDF5 makes blocks in sorted order, so after reading the explicit offset ordering is not the original unsorted order.
		% 2021.02.15 [12:02:36] - Updated support for files with datasets that contain 2D matrices.
	% TODO
		% DONE: Make support for duplicate frames more robust so minimize the number of file reads.

	[dataSubset, fid] = ciapkg.hdf5.readHDF5Subset(inputFilePath, offset, block,'passArgs', varargin);
end


classdef MimWSEditSlice < MimModel
    methods (Access = protected)
        function value = run(obj)
            value = struct();
        end

        function ValueHasChanged(obj, value)
            overlayImage = obj.Callback.getModelValue(obj.Parameters.segmentationVolumeId);
            imageSliceNumber = obj.Parameters.imageSliceNumber;
            axialDimension = obj.Parameters.axialDimension;
            disp('New value for seg slice:');
            disp(value);

            slice = overlayImage.GetSlice(imageSliceNumber, axialDimension);
            strokesArray = value.data;

            for stroke = strokesArray
                colour = MimWSEditSlice.stringToColour(stroke{1}.colour);
                pointsArray = stroke{1}.points;
                for point = pointsArray
                    x = point{1}.x;
                    y = point{1}.y;
                    slice(max(1, round(x)), max(1, round(y))) = colour;
                end
            end

            overlayImage.ReplaceImageSlice(slice, imageSliceNumber, axialDimension);
            obj.Callback.setModelValue(obj.Parameters.segmentationVolumeId, overlayImage.Copy());
        end
    end

    methods (Static, Access = private)
        function colourIndex = stringToColour(colourString)
            switch(colourString)
                case 'blue'
                    colourIndex = 1;
                case 'green'
                    colourIndex = 2;
                case 'red'
                    colourIndex = 3;
                case 'cyan'
                    colourIndex = 4;
                case 'magenta'
                    colourIndex = 5;
                case 'yellow'
                    colourIndex = 6;
                case 'grey'
                    colourIndex = 7;
                otherwise
                    colourIndex = 1;
            end
        end
    end
end


function varargout=load_gaimc_graph(graphname)
% LOAD_GAIMC_GRAPH Loads a graph from the gaimc library
%
% load_gaimc_graph is a helper function to load a graph provided with the
% library regardless of the current working directory.
%
% If it's called without any output arguments, it functions just like a
% load command executed on the .mat file with the graph.  If it's called
% with an output arguemnt, it functions just like a load command with
% output arguments.  It's somewhat complicated to explain because this is
% just a convinence function to make the examples work for any path, and
% not just from the gaimc root directory.
%
% Example:
%   % equivalent to load('graphs/airports.mat') run from the gaimc directory
%   load_gaimc_graph('airports')
%   % equivalent to P=load('graphs/kt-7-2.mat') run from the gaimc directory
%   P=load_gaimc_graph('kt-7-2.mat')
%   % so you don't have to put the path in for examples!

% David F. Gleich
% Copyright, Stanford University, 2008-2009

% History
% 2009-04-27: Initial coding

path=fileparts(mfilename('fullpath'));
if nargout==0
    evalin('caller',['load(''' fullfile(path,'graphs',graphname) ''');']);
else
    P = load(fullfile(path,'graphs',graphname));
    varargout{1} = P;
end


function drawopt = drawtrackresult(drawopt, fno, frame, tmpl, param)
% function drawopt = drawtrackresult(drawopt, fno, frame, tmpl, param, pts)
%
%   drawopt : misc info for drawing, intitially []
%         [.showcoef] : shows coefficient
%         [.showcondens,thcondens] : show condensation candidates
%   fno : frame number
%   frame(fh,fw) : current frame
%   tmpl.mean(th,tw) : mean image
%       .basis(tN,nb) : basis
%   param.est : current estimate
%        .wimg : warped image
%       [.err,mask] : error, mask image
%       [.param,conf] : condensation
%
% uses: util/showimgs

% Copyright (C) 2005 Jongwoo Lim and David Ross.
% All rights reserved.

  figure(1); clf;
  set(gcf,'DoubleBuffer','on','MenuBar','none');
  colormap('gray');

sz = size(tmpl.mean);

axes('position', [0 0 1 1])
imagesc(frame, [0,1]); hold on;

text(5, 18, num2str(fno), 'Color','y', 'FontWeight','bold', 'FontSize',18);
drawbox(sz(1:2), param.est, 'Color','r', 'LineWidth',2.5);
axis off; hold off;

drawnow;


function hmm = versCompatibilityFix(hmm)

if ~isfield(hmm.train,'orders')
    hmm.train.orders = formorders(hmm.train.order,hmm.train.orderoffset,...
        hmm.train.timelag,hmm.train.exptimelag);
end

if isfield(hmm,'cache')
    hmm = rmfield(hmm,'cache');
end

if isfield(hmm.train,'state')
    hmm.train = rmfield(hmm.train,'state');
end
if ~isfield(hmm.train,'onpower')
    hmm.train.onpower = 0;
end
if ~isfield(hmm.train,'leida')
    hmm.train.leida = 0;
end
if ~isfield(hmm.train,'embeddedlags')
    hmm.train.embeddedlags = 0;
end
if ~isfield(hmm.train,'embeddedlags_batched')
    hmm.train.embeddedlags_batched = 0;
end
if ~isfield(hmm.train,'pca')
    hmm.train.pca = 0;
end
if ~isfield(hmm.train,'filter')
    hmm.train.filter = [];
end
if ~isfield(hmm.train,'detrend')
    hmm.train.detrend = 0;
end
if ~isfield(hmm.train,'downsample')
    hmm.train.downsample = 0;
end
if ~isfield(hmm.train,'leakagecorr')
    hmm.train.leakagecorr = 0;
end
if ~isfield(hmm.train,'Pstructure')
    hmm.train.Pstructure = true(hmm.K);
end
if ~isfield(hmm.train,'Pistructure')
    hmm.train.Pistructure = true(1,hmm.K);
end
if ~isfield(hmm.train,'PriorWeightingP')
    hmm.train.PriorWeightingP = 1;
end
if ~isfield(hmm.train,'PriorWeightingPi')
    hmm.train.PriorWeightingPi = 1;
end
if ~isfield(hmm.train,'lowrank')
    hmm.train.lowrank = 0;
end
if ~isfield(hmm.train,'updateObs')
    hmm.train.updateObs = 1;
end
if ~isfield(hmm.train,'updateGamma')
    hmm.train.updateGamma = 1;
end
if ~isfield(hmm.train,'updateP')
    hmm.train.updateP = 1;
end
if ~isfield(hmm.train,'id_mixture')
    hmm.train.id_mixture = 0;
end
if ~isfield(hmm.train,'id_mixture')
    hmm.train.id_mixture = 0;
end
if ~isfield(hmm.train,'distribution')
    hmm.train.distribution = 'Gaussian';
end
if ~isfield(hmm.train,'pca_spatial')
    hmm.train.pca_spatial = 0;
end
if ~isfield(hmm.train,'Gamma_constraint')
    hmm.train.Gamma_constraint = [];
end
if ~isfield(hmm.train,'Gamma_constraint_radius')
    hmm.train.Gamma_constraint = [];
end
if ~isfield(hmm.train,'acrosstrial_constrained')
    hmm.train.acrosstrial_constrained = [];
end
if ~isfield(hmm.train,'episodic')
    hmm.train.episodic = 0;
end
end


% --------------------------------------------------------
% MDP Tracking
% Copyright (c) 2015 CVGL Stanford
% Licensed under The MIT License [see LICENSE for details]
% Written by Yu Xiang
% --------------------------------------------------------
function MOT_make_videos

is_save = 1;

opt = globals();
N = numel(opt.mot2d_test_seqs);

for seq_idx = 1:N
    close all;
    hf = figure(1);
    seq_name = opt.mot2d_test_seqs{seq_idx};
    seq_num = opt.mot2d_test_nums(seq_idx);

    % build the dres structure for images
    filename = sprintf('%s/%s_dres_image.mat', opt.results, seq_name);
    if exist(filename, 'file') ~= 0
        object = load(filename);
        dres_image = object.dres_image;
        fprintf('load images from file %s done\n', filename);
    else
        dres_image = read_dres_image(opt, seq_set, seq_name, seq_num);
        fprintf('read images done\n');
        save(filename, 'dres_image', '-v7.3');
    end

    % read tracking results
    filename = sprintf('results_MOT/results_MOT_1/%s.txt', seq_name);
    dres_track = read_mot2dres(filename);
    fprintf('read tracking results from %s\n', filename);
    ids = unique(dres_track.id);
    cmap = colormap(hsv(numel(ids)));
    cmap = cmap(randperm(numel(ids)),:);

    if is_save
        file_video = sprintf('results_MOT/results_MOT_1/%s.avi', seq_name);
        aviobj = VideoWriter(file_video);
        aviobj.FrameRate = 9;
        open(aviobj);
        fprintf('save video to %s\n', file_video);
    end

    for fr = 1:seq_num
        show_dres(fr, dres_image.I{fr}, '', dres_track, 2, cmap);
        if is_save
            writeVideo(aviobj, getframe(hf));
        else
            pause;
        end
    end

    if is_save
        close(aviobj);
    end
end


function HDR = mat2sel(FileName,fnout);
% MAT2SEL is useful for converting visual artifact selection
%   with gBSanalyze into BKR/EPS compliant *.SEL files.
%
%   HDR = mat2sel(infile, [selfile]);
%      infile   input filename (*.BKR or *.MAT are supported)
%      selfile  output file with info artifact selection [OPTIONAL]
%               if no selfile is provided, the information will be
%               stored with extension '.SEL' and the same filename and
%               path.
%
%   The artifact selection is available in HDR.ArtifactSelection and
%   can be also loaded with
%       [s,HDR]=sload(filename);
%   Currently, artifact selection of  EPS/BKR-Software and
%   gBSanalyze is supported.
%
% see also: SLOAD

%	$Revision: 1.1 $
%	$Id: mat2sel.m 2205 2009-10-27 12:18:15Z schloegl $
%	Copyright (C) 2004 by Alois Schloegl
%	a.schloegl@ieee.org

% This library is free software; you can redistribute it and/or
% modify it under the terms of the GNU Library General Public
% License as published by the Free Software Foundation; either
% Version 2 of the License, or (at your option) any later version.
%
% This library is distributed in the hope that it will be useful,
% but WITHOUT ANY WARRANTY; without even the implied warranty of
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
% Library General Public License for more details.
%
% You should have received a copy of the GNU Library General Public
% License along with this library; if not, write to the
% Free Software Foundation, Inc., 59 Temple Place - Suite 330,
% Boston, MA  02111-1307, USA.

[s,HDR] = sload(FileName);

if isfield(HDR,'ArtifactSelection'),
        artifact = HDR.ArtifactSelection;

        save([HDR.FILE.Name, '_artifact'], 'artifact');

        if nargin<2,
                fnout = [HDR.FILE.Name, '.sel']
                fnout = fullfile(HDR.FILE.Path,[HDR.FILE.Name, '.sel']);
        end
        if 0, exist(fnout)==2,
                fprintf(1,'File %s exists! Do you want to overwrite ',fnout);
                answer = input('[Y/N] ?');
        else
                answer = 'Y';
        end;
        if any(answer=='jJyYzZ'),
                fid = fopen(fnout,'w');
                fprintf(fid, '%i\r\n', artifact);
                fclose(fid);
        end;
end;


function resampScanNumV = getAssocResampledScanNum(scanNumV,planC)
% Return index of resampled scan created from input scanNum.
% ------------------------------------------------------------------------
% scanNumV   : Vector of scan nos.
% planC
% ------------------------------------------------------------------------
% AI 5/19/22

indexS = planC{end};

scanUIDc = {planC{indexS.scan}(scanNumV).scanUID};

resampScanNumV = nan(1,length(scanNumV)); % Initialize
for nScan = 1:length(planC{indexS.scan})
    baseScanUID = planC{indexS.scan}(nScan).assocBaseScanUID;
    assocIdxV = ismember(scanUIDc,baseScanUID);
    if any(assocIdxV)
        resampScanNumV(scanNumV==find(assocIdxV)) = nScan;
    end
end
resampScanNumV = resampScanNumV(~isnan(resampScanNumV));

end


function test_checkcode

% WALLTIME 00:20:00
% MEM 2gb
% DEPENDENCY

% see http://bugzilla.fieldtriptoolbox.org/show_bug.cgi?id=3309

% this is a subset of the LINT warnings, I removed the ones about unused variables
%
%     ''findstr' is not recommended. Use 'strfind' instead.'
%     ''flipdim' is not recommended. Use 'flip' instead.'
%     ''isempty(strfind(str1, str2))' is not recommended. Use '~contains(str1, str2)' instead.'
%     ''isequalwithequalnans' is not recommended. Use 'isequaln' instead.'
%     ''resampled_tsdif' produces a value that might be unused.'
%     ''skip' produces a value that might be unused.'
%     ''spectrum.welch' is not recommended. Use 'pwelch' instead.'
%     ''strfind(str1, str2)' is not recommended. Use 'contains(str1, str2)' instead.'
%     ''strread' is not recommended. Use 'textscan' instead.'
%     ''textread' is not recommended. Use 'textscan' instead.'
%     ''||' produces a value that might be unused.'
%     ''~cellfun('isempty', strfind(str1, str2))' is not recommended. Use 'contains(str1, str2)' instead.'
%     ''~cellfun(@isempty, strfind(str1, str2))' is not recommended. Use 'contains(str1, str2)' instead.'
%     ''~isempty(strfind(str1, str2))' is not recommended. Use 'contains(str1, str2)' instead.'
%     'Assign the onCleanup output argument to a variable. Do not use the tilde operator (~) in place of a variable.'
%     'Best practice is to separate output variables with commas.'
%     'Calling AXES(h) in a loop can be slow. Consider moving the call to AXES outside the loop.'
%     'Consider replacing GRIDDATA with SCATTEREDINTERPOLANT for better performance.'
%     'Consider using ISA instead of comparing the class name.'
%     'Consider using newline, semicolon, or comma before this statement for readability.'
%     'DISP(SPRINTF(...)) can usually be replaced by FPRINTF(...\n).'
%     'ERROR takes SPRINTF-like arguments directly.'
%     'EXIST with two input arguments is generally faster and clearer than with one input argument.'
%     'Extra comma is unnecessary in CASE statement before newline.'
%     'Extra comma is unnecessary in CATCH statement before newline.'
%     'Extra comma is unnecessary in ELSE statement before newline.'
%     'Extra comma is unnecessary in ELSEIF statement before newline.'
%     'Extra comma is unnecessary in END statement before newline.'
%     'Extra comma is unnecessary in FOR statement before newline.'
%     'Extra comma is unnecessary in IF statement before newline.'
%     'Extra comma is unnecessary in OTHERWISE statement before newline.'
%     'Extra comma is unnecessary in SWITCH statement before newline.'
%     'Extra comma is unnecessary in WHILE statement before newline.'
%     'Extra comma is unnecessary.'
%     'Extra semicolon is unnecessary in CASE statement before newline.'
%     'Extra semicolon is unnecessary in ELSE statement before newline.'
%     'Extra semicolon is unnecessary in ELSEIF statement before newline.'
%     'Extra semicolon is unnecessary in END statement before newline.'
%     'Extra semicolon is unnecessary in FOR statement before newline.'
%     'Extra semicolon is unnecessary in FUNCTION statement before newline.'
%     'Extra semicolon is unnecessary in IF statement before newline.'
%     'Extra semicolon is unnecessary in WHILE statement before newline.'
%     'Extra semicolon is unnecessary.'
%     'FOR might not be aligned with its matching END (line 169).'
%     'FOR might not be aligned with its matching END (line 31).'
%     'FOR might not be aligned with its matching END (line 437).'
%     'FOR might not be aligned with its matching END (line 81).'
%     'FREAD(FID,...,'*char') is more efficient than CHAR(FREAD(...)).'
%     'For improved robustness, consider replacing i and j by 1i.'
%     'For readability and performance, consider using 'newline' instead of 'sprintf('\n')'.'
%     'Function ERROR might be called with too few arguments.'
%     'IF might not be aligned with its matching END (line 172).'
%     'IF might not be aligned with its matching END (line 30).'
%     'IF might not be aligned with its matching END (line 37).'
%     'IF might not be aligned with its matching END (line 54).'
%     'INV(A)*b can be slower and less accurate than A\b. Consider using A\b for INV(A)*b or b/A for b*INV(A).'
%     'Instead of using transpose ('), consider using a different DIMENSION input argument to MAX.'
%     'Instead of using transpose ('), consider using a different DIMENSION input argument to MEAN.'
%     'Instead of using transpose ('), consider using a different DIMENSION input argument to MIN.'
%     'Instead of using transpose ('), consider using a different DIMENSION input argument to SUM.'
%     'LASTERR and LASTERROR are not recommended. Use an identifier on the CATCH block instead.'
%     'Loop index 'i' is changed inside of a FOR loop.'
%     'NARGCHK is not recommended. Use NARGINCHK without ERROR instead.'
%     'NUMEL(x) is usually faster than PROD(SIZE(x)).'
%     'Operator '+' is seldom used in a logical context.'
%     'Overloading DISPLAY is not recommended.'
%     'Parentheses are not needed in a FOR statement.'
%     'Parse error at FOR: usage might be invalid MATLAB syntax.'
%     'Possible inappropriate use of - operator. Use = if assignment is intended.'
%     'RAND or RANDN with the 'seed', 'state', or 'twister' inputs is not recommended. Use RNG instead.'
%     'STRMATCH is not recommended. Use STRCMP instead.'
%     'STRMATCH is not recommended. Use STRNCMP or VALIDATESTRING instead.'
%     'TRY might not be aligned with its matching END (line 299).'
%     'TRY statement should have a CATCH statement to check for unexpected errors.'
%     'Terminate statement with semicolon to suppress output (in functions).'
%     'Terminate statement with semicolon to suppress output (within a script).'
%     'The comparison will likely fail due to case mismatch.'
%     'The format might not agree with the argument count.'
%     'The warning with tag 'MATLAB:divideByZero' has been removed from MATLAB, so this statement has no effect.'
%     'This sparse indexing expression is likely to be slow.'
%     'This statement (and possibly following ones) cannot be reached.'
%     'This use of MAT2CELL should probably be replaced by a simpler, faster call to NUM2CELL.'
%     'To improve performance, replace ISEMPTY(FIND(X)) with ISEMPTY(FIND( X, 1 )).'
%     'Unexpected use of ' in a scalar context.'
%     'Unexpected use of '.*' in a scalar context.'
%     'Unexpected use of '.^' in a scalar context.'
%     'Unexpected use of '[' in a scalar context.'
%     'Use FIND with the 'first' or 'last' option.'
%     'Use STRCMP instead of == or ~= to compare character vectors.'
%     'Use STRCMPI(str1,str2) instead of using UPPER/LOWER in a call to STRCMP.'
%     'Use STRTRIM(str) instead of nesting FLIPLR and DEBLANK calls.'
%     'Use dynamic fieldnames with structures instead of GETFIELD.'
%     'Use dynamic fieldnames with structures instead of SETFIELD.'
%     'Use of 'nargout' in a script will be removed in a future release.'
%     'Use of brackets [] is unnecessary. Use parentheses to group, if needed.'
%     'Use one call to TEXTSCAN instead of calling STRTOK in a loop.'
%     'Using ISEMPTY is usually faster than comparing LENGTH to 0.'
%     'Using REGEXP(str, pattern, 'ONCE') is faster in this case.'
%     'Variable 'ElecPres' might be set by a nonlogical operator.'
%     'Variable 'ElecPres' might be set by a nonscalar operator.'
%     'Variable 'H', apparently a structure, is changed but the value seems to be unused.'
%     'Variable 'Npoints' might be set by a nonscalar operator.'
%     'Variable 'Nyq' might be set by a nonscalar operator.'
%     'Variable 'binwidth' might be set by a nonscalar operator.'
%     'Variable 'cfg', apparently a structure, is changed but the value seems to be unused.'
%     'Variable 'cond' might be set by a nonlogical operator.'
%     'Variable 'cond' might be set by a nonscalar operator.'
%     'Variable 'hdr', apparently a structure, is changed but the value seems to be unused.'
%     'Variable 'info', apparently a structure, is changed but the value seems to be unused.'
%     'Variable 'locktrllen' might be set by a nonscalar operator.'
%     'Variable 'nchans' might be set by a nonscalar operator.'
%     'Variable 'ncmb' might be set by a nonscalar operator.'
%     'Variable 'npad' might be set by a nonscalar operator.'
%     'Variable 'origrank' might be set by a nonscalar operator.'
%     'Variable 'surface', apparently a structure, is changed but the value seems to be unused.'
%     'WARNING takes SPRINTF-like arguments directly.'
%     'When checking if a variable is a matrix consider using ISMATRIX.'
%     '{ A{:} B } can often be replaced by [ A {B}], which can be much faster.'

%%

[v, p] = ft_version;
p = {p};
f = {};

while ~isempty(p)
  fprintf('looking in directory %s\n', p{1});
  nf = dir(p{1});
  p(1) = []; % remove this one
  for i=1:numel(nf)
    if nf(i).isdir && ~isequal(nf(i).name(1), '.') && ~isequal(nf(i).name, 'external') && ~isequal(nf(i).name, 'test')
      p{end+1} = fullfile(nf(i).folder, nf(i).name);
    else
      if ~nf(i).isdir && nf(i).name(end-1)=='.' && nf(i).name(end)=='m'
        f{end+1} = fullfile(nf(i).folder, nf(i).name);
      end
    end
  end
end

filelist = f;
clear f p

fprintf('found %d *.m files\n', numel(filelist));

%%

invalid = {
  'Use || instead of | as the OR operator in (scalar) conditional statements.'
  'Use && instead of & as the AND operator in (scalar) conditional statements.'
  'is known to MATLAB by its file name'
  'Use ISCELL instead of comparing the class to ''cell''.'
  'Use ISLOGICAL instead of comparing the class to ''logical''.'
  'Use TRUE or FALSE instead of LOGICAL(1) or LOGICAL(0).'
  'Best practice is to separate output variables with commas.'
  };

m = {};
for i=1:numel(filelist)
  f = filelist{i};
  s = checkcode(f);
  m = union(m, {s.message});
  for j=1:numel(s)
    for k=1:numel(invalid)
      if contains(s(j).message, invalid{k})
        % pretty display
        fprintf('================================================================================\n');
        fprintf('ERROR: %s\n', f); % invalid{k});
        fprintf('================================================================================\n');

        checkcode(f)
        % error('please fix line %d in %s', s(j).line, f);
      end
    end
  end
end

for i=1:numel(filelist)
  f = filelist{i};
  assert(checktilde(f), sprintf('Do not use tilde to ignore outputs in %s', f));
end

%%

% this shows the full list of warning messages
if false
  disp(m);
end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% SUBFUNCTION, see https://github.com/fieldtrip/fieldtrip/issues/831
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function bool = checktilde(filename)
[p, f, x] = fileparts(filename);
if ismember(f, {'matlab2markdown', 'markdown2matlab'})
  % these are known false positives, they should not trigger an error
  bool = true;
  return
end
fid = fopen(filename, 'r');
str = fread(fid, [1, inf], 'char=>char');
bool = true; % this is the return value when all is fine
bool = bool & isempty(regexp(str, '\[~ ', 'once'));
bool = bool & isempty(regexp(str, '\[~,', 'once'));
bool = bool & isempty(regexp(str, '~\]', 'once'));
bool = bool & isempty(regexp(str, ', *~ *,', 'once'));
fclose(fid);


function data = json_decode(string)
% json_decode Parses a JSON string
%
% This function parses a JSON string and returns a cell array with the
% parsed data. JSON objects are converted to structures and JSON arrays are
% converted to cell arrays.
%
% Input:
% - string (string): A JSON string
%
% Output:
% - data (struct, cell): A decoded Matlab structure
%
% Credits: Based on the code of F. Glineur, 2009 (Available on Matlab File
% Exchange)

pos = 1;
len = length(string);
% String delimiters and escape characters are identified beforehand to improve speed
esc = regexp(string, '["\\]'); index_esc = 1; len_esc = length(esc);

if pos <= len
    switch(next_char)
        case '{'
            data = parse_object;
        case '['
            data = parse_array;
        otherwise
            error_pos('Outer level structure must be an object or an array');
    end
end

    function object = parse_object
        parse_char('{');
        object = [];
        if next_char ~= '}'
            while 1
                str = parse_string;
                if isempty(str)
                    error_pos('Name of value at position %d cannot be empty');
                end
                parse_char(':');
                val = parse_value;
                object.(valid_field(str)) = val;
                if next_char == '}'
                    break;
                end
                parse_char(',');
            end
        end
        parse_char('}');
    end

    function object = parse_array
        parse_char('[');
        object = cell(0, 1);
        if next_char ~= ']'
            while 1
                val = parse_value;
                object{end+1} = val;
                if next_char == ']'
                    break;
                end
                parse_char(',');
            end
        end
        parse_char(']');
    end

    function parse_char(c)
        skip_whitespace;
        if pos > len || string(pos) ~= c
            error_pos(sprintf('Expected %c at position %%d', c));
        else
            pos = pos + 1;
            skip_whitespace;
        end
    end

    function c = next_char
        skip_whitespace;
        if pos > len
            c = [];
        else
            c = string(pos);
        end
    end

    function skip_whitespace
        while pos <= len && isspace(string(pos))
            pos = pos + 1;
        end
    end

     function str = parse_string
        if string(pos) ~= '"'
            error_pos('String starting with " expected at position %d');
        else
            pos = pos + 1;
        end
        str = '';
        while pos <= len
            while index_esc <= len_esc && esc(index_esc) < pos
                index_esc = index_esc + 1;
            end
            if index_esc > len_esc
                str = [str string(pos:end)];
                pos = len + 1;
                break;
            else
                str = [str string(pos:esc(index_esc)-1)];
                pos = esc(index_esc);
            end
            switch string(pos)
                case '"'
                    pos = pos + 1;
                    return;
                case '\'
                    if pos+1 > len
                        error_pos('End of file reached right after escape character');
                    end
                    pos = pos + 1;
                    switch string(pos)
                        case {'"' '\' '/'}
                            str(end+1) = string(pos);
                            pos = pos + 1;
                        case {'b' 'f' 'n' 'r' 't'}
                            str(end+1) = sprintf(['\' string(pos)]);
                            pos = pos + 1;
                        case 'u'
                            if pos+4 > len
                                error_pos('End of file reached in escaped unicode character');
                            end
                            str(end+1:end+6) = string(pos-1:pos+4);
                            pos = pos + 5;
                    end
                otherwise % should never happen
                    str(end+1) = string(pos);
                    pos = pos + 1;
            end
        end
        error_pos('End of file while expecting end of string');
    end

    function num = parse_number
        [num, ~, err, delta] = sscanf(string(pos:min(len,pos+20)), '%f', 1); % TODO : compare with json(pos:end)
        if ~isempty(err)
            error_pos('Error reading number at position %d');
        end
        pos = pos + delta-1;
    end

    function val = parse_value
        switch(string(pos))
            case '"'
                val = parse_string;
                return;
            case '['
                val = parse_array;
                return;
            case '{'
                val = parse_object;
                return;
            case {'-','0','1','2','3','4','5','6','7','8','9'}
                val = parse_number;
                return;
            case 't'
                if pos+3 <= len && strcmpi(string(pos:pos+3), 'true')
                    val = true;
                    pos = pos + 4;
                    return;
                end
            case 'f'
                if pos+4 <= len && strcmpi(string(pos:pos+4), 'false')
                    val = false;
                    pos = pos + 5;
                    return;
                end
            case 'n'
                if pos+3 <= len && strcmpi(string(pos:pos+3), 'null')
                    val = [];
                    pos = pos + 4;
                    return;
                end
        end
        error_pos('Value expected at position %d');
    end

    function error_pos(msg)
        poss = max(min([pos-15 pos-1 pos pos+20],len),1);
        if poss(3) == poss(2)
            poss(3:4) = poss(2)+[0 -1];         % display nothing after
        end
        msg = [sprintf(msg, pos) ' : ... ' string(poss(1):poss(2)) '<error>' string(poss(3):poss(4)) ' ... '];
        ME = MException('JSONparser:invalidFormat', msg);
        throw(ME);
    end

    function str = valid_field(str)
    % From MATLAB doc: field names must begin with a letter, which may be
    % followed by any combination of letters, digits, and underscores.
    % Invalid characters will be converted to underscores, and the prefix
    % "alpha_" will be added if first character is not a letter.
        if ~isletter(str(1))
            str = ['alpha_' str];
        end
        str(~isletter(str) & ~('0' <= str & str <= '9')) = '_';
    end

end


function buffername = onl_writeLslToFile(varargin)
% Open a named LSL stream and write it to disk.
% If no arguments are supplied, a GUI is generated.
% This function requires the BCILAB toolbox (http://sccn.ucsd.edu/wiki/BCILAB)
%
% --------------------------------------------------------------------------------------------------
% Input                  Information
% --------------------------------------------------------------------------------------------------
% StreamName:            LSL stream that should be written to disk
%                        This is the name of the stream.
%                        Possible values: ''
%                        Default value  : 'n/a'
%                        Input Data Type: string
%
% OutputOptions:         Output options
%
%     | FileName:        The file to write to. This may also contain a system or platform-independent path
%                        Input Range  : Unrestricted
%                        Default value: 'data:/lastdata.set'
%                        Input Data Type: string
%
%     | UpdateFrequency: Update frequency
%                        This is the rate at which data is written.
%                        Input Range  : Unrestricted
%                        Default value: 1
%                        Input Data Type: real number (double)
%
%     | StartDelay:      Start-up delay
%                        Delay before real-time processing begins; grace period until file is written.
%                        Input Range  : Unrestricted
%                        Default value: 3
%                        Input Data Type: real number (double)
%
%
%
% See Also: run_writedataset(), run_readlsl()
%
% Tim Mullen, SCCN/INC/UCSD 2013
% Inspired by BCILAB functions by Christian Kothe

% make sure that everything is on the path and LSL is loaded
if ~exist('arg_define','file')
    addpath(genpath(fileparts(mfilename('fullpath')))); end
% make sure that the library is also found if the toolbox is compiled
lib = lsl_loadlib(env_translatepath('dependencies:/liblsl-Matlab/bin'));

% handle default arguments
try
streamnames = find_streams;
catch err
    hlp_handleerror(err);
    streamnames = {''};
end
% handle inputs
opts = arg_define(varargin, ...
        arg({'streamname','StreamName'},streamnames{1},streamnames,'LSL stream that should be written to disk. This is the name of the stream.'), ...
        arg_sub({'outopts','OutputOptions'},{'out_filename','data:/lastdata.set'},@run_writedataset,'Output options','suppress',{'in_stream'}) ...
        );

if isempty(opts.streamname)
    return;
end
if isempty(varargin)
    % bring up GUI dialog if no arguments were passed
    arg_guidialog;
else
    % choose variable names to hold the stream's data (in the base workspace)
    taken = evalin('base','whos(''lsl*'')');
    buffername = genvarname(['lsl_' opts.streamname '_stream'],{taken.name});

    % create local stream to read data from LSL
    run_readlsl('MatlabStream',buffername,'SelectionProperty','name','SelectionValue',opts.streamname);

    % write data to file
    fprintf('Will write stream ''%s'' to %s\n',opts.streamname,env_translatepath(opts.outopts.out_filename));
    fprintf('To end recording, type:\n>> clear %s \n',buffername);
    run_writedataset(opts.outopts,'in_stream',buffername)
end

% find names of streams on the lab network...
function names = find_streams
    streams = lsl_resolve_all(lib,1);
    names = cellfun(@(s)s.name(),streams ,'UniformOutput',false);
    if isempty(names)
        error('There is no stream visible on the network.'); end

end

end


% This is an example of how to write the config file
% Line 1 - name of the strategy
% Line 2 - number of Parameters
% Line 3 - Name of Parameter 1
% Line 4 - Default Value of Parameter 1
% ...(do not leave an empty line)
Template Algorithm
3
p1
0
p2
1
p3
10


function create(obj,wrt)
% Create a NIFTI-1 file
% FORMAT create(obj)
% This writes out the header information for the nifti object
%
% create(obj,wrt)
% This also writes out an empty image volume if wrt==1
% _______________________________________________________________________
% Copyright (C) 2008 Wellcome Trust Centre for Neuroimaging

%
% $Id$

for i=1:numel(obj)
    create_each(obj(i));
end;

function create_each(obj)
if ~isa(obj.dat,'file_array'),
    error('Data must be a file-array');
end;
fname = obj.dat.fname;
if isempty(fname),
    error('No filename to write to.');
end;
dt = obj.dat.dtype;
ok = write_hdr_raw(fname,obj.hdr,dt(end-1)=='B');
if ~ok,
    error(['Unable to write header for "' fname '".']);
end;

write_extras(fname,obj.extras);

if nargin>2 && any(wrt==1),
    % Create an empty image file if necessary
    d   = findindict(obj.hdr.datatype, 'dtype');
    dim = double(obj.hdr.dim(2:end));
    dim((double(obj.hdr.dim(1))+1):end) = 1;
    nbytes = ceil(d.size*d.nelem*prod(dim(1:2)))*prod(dim(3:end))+double(obj.hdr.vox_offset);
    [pth,nam,ext] = fileparts(obj.dat.fname);

    if any(strcmp(deblank(obj.hdr.magic),{'n+1','nx1'})),
        ext = '.nii';
    else
        ext = '.img';
    end;
    iname = fullfile(pth,[nam ext]);
    fp    = fopen(iname,'a+');
    if fp==-1,
        error(['Unable to create image for "' fname '".']);
    end;

    fseek(fp,0,'eof');
    pos = ftell(fp);
    if pos<nbytes,
        bs      = 2048; % Buffer-size
        nbytes  = nbytes - pos;
        buf     = uint8(0);
        buf(bs) = 0;
        while(nbytes>0)
            if nbytes<bs, buf = buf(1:nbytes); end;
            nw = fwrite(fp,buf,'uint8');
            if nw<min(bs,nbytes),
                fclose(fp);
                error(['Problem while creating image for "' fname '".']);
            end;
            nbytes = nbytes - nw;
        end;
    end;
    fclose(fp);
end;

return;


function varargout = setStateName(obj,varargin)
    nVarargs = length(varargin);
    name = varargin{1};
    if nVarargs == 2
        index = varargin{2};
    else
        index = 1:obj.dim.x;
    end
    varargout = cell(size(index));
    j = 1;
    for i=index
        obj.x(i) = sym(name{j});
        varargout{j} = sym(name{j});
        j = j+1;
    end
end