% SHOW_EVENTS - Display events in epochs. Events selected by 
%                 MAKE_TIMEWARP function can be optionally highlighted.
%                 Each epoch is visualized as a row in the output image with
%                 events marked by colored rectangles.
%
% Usage:
%       >> im = show_events(EEG, 'key1',value1,'key2',value2,...);
%
% Inputs:
%
%   EEG                 - dataset structure.
%
%
% Optional inputs (in 'key', value format):
%
%   'eventThicknessCoef'  - (positive number) adjust the thickness of event 
%                           markers in the image. The default value is 1. 
%                           Lower values reduce thickness and higher values 
%                           increase it. For example, to decrease event
%                           rectangle width by half, set this parameter to
%                           0.5. {default: 1}
%   'eventNames'          - a cell array containing names of events to be
%                           displayed. Order and repetition are ignored. 
%                           {default: all events if timeWarp is not provided)
%
%   'timeWarp'            - a structure with latencies (time-warp matrix
%                           for newtimef function) and epochs with correct
%                           sequence created by MAKE_TIMEWARP function.
%                           the subsets {default: false|0)
% Outputs:
%
%   im                    - color (RGB) image showing events of interest in
%                           epochs.
%
% Example:
%
%   % To display all events in data structure EEG
%   >> show_events(EEG);
%
%   % To highlight events selected by MAKE_TIMEWARP function with thin
%   %  event markers
%  >> show_events(EEG, 'eventThicknessCoef', 0.5, 'eventNames', timeWarp.eventSequence,
%   'timeWarp', timeWarp);
%
% Version 1.1
% Author: Nima Bigdely Shamlo, SCCN/INC/UCSD, 2008
% See also: MAKE_TIMEWARP, NEWTIMEF

% Copyright (C) Nima Bigdely Shamlo, SCCN/INC/UCSD, 2008
%
% 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  im = show_events(EEG, varargin)


%% check inputs
EEG = change_events_to_string(EEG);

inputKeyValues = finputcheck(varargin, ...
   {'eventThicknessCoef'     'real'    [0 Inf]          1; ...
    'eventNames'     'cell'    {}        {}  ; ...
    'timeWarp'        'struct'    []          struct([]); ...
    });

eventThicknessCoef = 0;
eventNames = {};
timeWarp=[];

% place key values into function workspace variables
inputKeyValuesFields = fieldnames(inputKeyValues);
for i=1:length(inputKeyValuesFields)
    eval([inputKeyValuesFields{i} '= inputKeyValues.' inputKeyValuesFields{i} ';']);
end

if length(eventNames) == 0
    if length(timeWarp) == 0
        eventNames = [];
        for i=1:length(EEG.epoch)
            eventNames =  [eventNames EEG.epoch(i).eventtype];
        end
    else
        for i=1:length(timeWarp.eventSequence)
            if ischar(timeWarp.eventSequence{i})
                eventNames = [eventNames timeWarp.eventSequence{i}];
            else % in case it is a cell of strings
                for j=1:length(timeWarp.eventSequence{i})
                    eventNames = [eventNames timeWarp.eventSequence{i}{j}];
                end
            end
        end
        %        eventNames = timeWarp.eventSequence; % if event names is not provided only show timeWarp events
    end
end

if length(timeWarp) == 0
    timeWarp=[];
    timeWarp.latencies = [];
    timeWarp.epochs = 1:length(EEG.epoch);
end
%% set image parameters

imWidth = 300*4;
imHeight = 240*4;
im = double(zeros(imHeight, imWidth, 3));

%% find latencies for events of interest in each epoch

uniqueEventNames = unique_cell_string(eventNames);
for epochNumber = 1:length(EEG.epoch)
    for eventNumber = 1:length(uniqueEventNames)
       ids = find(strcmp(EEG.epoch(epochNumber).eventtype,uniqueEventNames{eventNumber})); % index for events with this type in the current epoch
       latency{epochNumber, eventNumber} = cell2mat(EEG.epoch(epochNumber).eventlatency(ids));
    end
end

%% find a good default value for event marker width based on quantiles
% of inter-event latencies.

intervals = [];  
for i=1:size(latency,1) 
    intervals = [intervals diff(sort(cell2mat(latency(i,:)),'ascend'))]; %#ok<AGROW>
end
 
if isempty(intervals)
    eventLineWidth = 0.05*eventThicknessCoef;
else
    eventLineWidth = round(eventThicknessCoef * 0.5 * imWidth * -quantile(-intervals,0.8) /  ((EEG.xmax - EEG.xmin)*1000));
end


%% place colored rectangle block o image to display events
eventColors = lines(length(uniqueEventNames));
for epochNumber = 1:length(EEG.epoch)
    for eventNumber = 1:length(uniqueEventNames)
        for eventInstanceNumber = 1:length(latency{epochNumber, eventNumber})
            startHeight =  max(1,round((epochNumber - 1) * imHeight/ length(EEG.epoch)));
            endHeight = min(imHeight, round((epochNumber) * imHeight/ length(EEG.epoch)));
            startWidth = max(1,round(-eventLineWidth/2 + imWidth * (latency{epochNumber, eventNumber}(eventInstanceNumber) - (EEG.xmin*1000))/ ((EEG.xmax - EEG.xmin)*1000)));
            endWidth =  min(imWidth,startWidth + eventLineWidth);
            colorForEvent =  eventColorBasedOnTypeAndAcceptance(eventColors, eventNumber, epochNumber, latency{epochNumber, eventNumber}(eventInstanceNumber), timeWarp);
            for c = 1:3 % to make sure the highlighted events are not overwritten by other events in the image, we use 'max' and give the brightest color priority               
                im(startHeight:endHeight,startWidth:endWidth,c) = max(im(startHeight:endHeight,startWidth:endWidth,c), colorForEvent(c));
            end
        end
    end
end

%% plot image with legend and axis labels

figure;
hold on;
for i=1:length(uniqueEventNames)
    plot([0 0],rand * [0 0],'Color',eventColors(i,:),'linewidth',10);
    uniqueEventNames{i} = strrep(uniqueEventNames{i},'_','-'); % replace _ with - so it is displayed correctly in the legend
end

legend(uniqueEventNames, 'Location', 'NorthWest');


image(round([EEG.xmin*1000 EEG.xmax*1000 ]), [1 length(EEG.epoch)],im);
xlim(round([EEG.xmin*1000 EEG.xmax*1000 ]))
ylim([1 length(EEG.epoch)])
axis normal;
xlabel('Latency', 'fontsize',16);
xlabel('Latency', 'fontsize',16);
ylabel('Epochs', 'fontsize',16);

if nargout == 0  % suppress output if not requested
    clear im ;
end
    


function color = eventColorBasedOnTypeAndAcceptance(eventColors, eventNumber, epochNumber, eventLatency,timeWarp)
accepted = ismember_bc(epochNumber, timeWarp.epochs);
if ~isempty(timeWarp.latencies)
    matchedEpoch = find(timeWarp.epochs ==  epochNumber);
    accepted = accepted && ~isempty(find(timeWarp.latencies(matchedEpoch,:) == eventLatency, 1));
end

color = eventColors(eventNumber,:);

if ~accepted
    color = color*0.3; % dim the color of unaccepted events.
end

function EEG = change_events_to_string(EEG)
needChange = false; 
for i=1:length(EEG.event)
    if ~ischar(EEG.event(i).type)
        EEG.event(i).type = num2str( EEG.event(i).type );
        needChange = true;
    end
end

if needChange
    for e=1:length(EEG.epoch)
        for i=1:length(EEG.epoch(e).eventtype)
            EEG.epoch(e).eventtype(i) = {num2str(cell2mat(EEG.epoch(e).eventtype(i)))};
        end
    end
end
