function [init_idx, max_d1o, a_plot, fail_cond] = ...
      calcInitVmMaxCurvPhasePlane(s, max_idx, min_idx, plotit)

% calcInitVmMaxCurvPhasePlane - Calculates the voltage at the maximum curvature in the phase plane as action potential threshold.
%
% Usage:
% [init_idx, max_d1o, a_plot, fail_cond] = 
%	calcInitVmMaxCurvPhasePlane(s, max_idx, min_idx, plotit)
%
% Description:
%   First take the phase-plane v'-v from the beginning to max(v'). Then regulate 
% intervals by interpolation. Point of maximum curvature: Kp = V''[1 + (V')^2]^(-3/2)
% Taken from Sekerli, Del Negro, Lee and Butera. 
% IEEE Trans. Biomed. Eng., 51(9): 1665-71, 2004.
% 
%   Parameters:
%	s: A spike_shape object.
%	max_idx: The index of the maximal point of the spike_shape [dt].
%	min_idx: The index of the minimal point of the spike_shape [dt].
%	plotit: If non-zero, plot a graph annotating the test results 
%		(optional).
%
%   Returns:
%	init_idx: AP threshold index in the spike_shape [dt].
%	max_d1o: Maximal value of first voltage derivative [dy].
%	a_plot: plot_abstract, if requested.
%	fail_cond: True if this algorithm fails to be trustable.
%
% See also: calcInitVm
%
% $Id$
%
% Author: 
%   Cengiz Gunay <cgunay@emory.edu>, 2005/04/12
%   Inspired by Sekerli, Del Negro, Lee and Butera. IEEE Trans. Biomed. Eng.,
%	51(9): 1665-71, 2004 and by personal communication with Murat Şekerli.

% Copyright (c) 2007 Cengiz Gunay <cengique@users.sf.net>.
% This work is licensed under the Academic Free License ("AFL")
% v. 3.0. To view a copy of this license, please look at the COPYING
% file distributed with this software or visit
% http://opensource.org/licenses/afl-3.0.php.

if ~ exist('plotit', 'var')
  plotit = 0;
end
a_plot = [];

% Apply a median filter to reduce noise
%medfilt1(s.trace.data, windowsize);
windowsize = 6;
%filter(ones(1, windowsize)/windowsize, 1, s.trace.data); 
smooth_data = medfilt1([s.trace.data(1); s.trace.data], windowsize);
smooth_data = smooth_data(2:end); % drop first sample which has noise from medfilt1

%end
% Decimate using FIR low-pass filter (doesn't work: adds ringing artifact)
dec_factor = 1;
trace_data = smooth_data;
%trace_data = decimate(smooth_data, dec_factor, min(floor(length(smooth_data)/3), 30), 'FIR');

%d2 = diff2T(trace_data(1 : (max_idx + 2)) * s.trace.dy, s.trace.dt);
d1o = diffT(trace_data(1 : (floor(max_idx/dec_factor) + 2)) * s.trace.dy, ...
	    s.trace.dt * dec_factor);
%d2 = d2(3:(end -2));
d1o = d1o(3:(end -2));

% Find max in derivative
[max_d1o, max_d1o_idx] = max(d1o);

% Find all positive part in derivative until voltage peak
last_pos_d = find(d1o >= 0);
if length(last_pos_d) == 0
  error('calcInitVm:failed', 'No positive slope before peak of %s.', ...
	get(s, 'id'));
else
  last_pos_d_idx = last_pos_d(end);
end

% put arbitrary negativeness limit on slope
last_neg_d = find(d1o(1:last_pos_d_idx) < -1);
if length(last_neg_d) == 0
  last_neg_d_idx = 1;
elseif last_neg_d(end) == last_pos_d_idx   % can never happen?
  error('calcInitVm:failed', 'Negative slope right before peak of %s.', ...
	get(s, 'id'));
else
  last_neg_d_idx = last_neg_d(end);
end

% Find min in voltage before end
[min_v, min_v_idx] = min(trace_data(1 : max_d1o_idx));

% Interpolate to find a regular intervalled phase-plane representation
% Voltage range is from the dip in voltage point to the spike peak.
% Tried both spline and pchip, spline overfits.
num_points = 200;
start_idx = max(1, last_neg_d_idx - 1);
dv = (trace_data(max_d1o_idx + 2) - trace_data(start_idx + 2))/num_points;
voltage_points = ...
    (trace_data(start_idx + 2):dv:trace_data(max_d1o_idx + 2)) * s.trace.dy;

% Arbitrarily remove duplicate values from voltage values
trace_data((start_idx + 2) : (max_d1o_idx + 2));
[orig_v_points unique_idx] = ...
    unique(trace_data((start_idx + 2) : (max_d1o_idx + 2)) * s.trace.dy);
d1o_points = d1o(start_idx:max_d1o_idx);

% Check if there are enough points to interpolate
if length(unique_idx) < 2
  error('calcInitVm:failed', 'Less than two vPP data points in the stem of %s.', ...
	get(s, 'id'));
end

if length(voltage_points) < 2
  error('calcInitVm:failed', 'Less than two v data points in the stem of %s.', ...
	get(s, 'id'));
end

interp_vpp = pchip(orig_v_points, d1o_points(unique_idx), voltage_points);

% Put lower bound on vpp?
windowsize = 10;
filtered_vpp = filter(ones(1, windowsize)/windowsize, 1, interp_vpp);
low_bound = find(filtered_vpp >= 1); % mV/ms crossing is the lower bound

%figure; plot(orig_v_points * s.trace.dy, d1o_points(unique_idx), ...
%	     voltage_points, [interp_vpp; filtered_vpp]'); 
%legend('orig', 'interp', 'filtered');

if length(low_bound) > 0
  interp_vpp = interp_vpp(low_bound(1):end);
  voltage_points = voltage_points(low_bound(1):end);
end

if (1 == 1) 
  % Estimations from Taylor series, error = O(dv^4)
  d1 = diffT(interp_vpp, dv);
  d2 = diff2T_h4(interp_vpp, dv);
  d3 = diff3T_h4(interp_vpp, dv);
  d1 = d1(3:(end -2));
  d2 = d2(3:(end)-2);
  d3 = d3(3:(end)-2);
else
  d1 = diff(interp_vpp);
  d2 = diff(d1);
  d3 = diff(d2);
  d1 = d1(2:(end -2));
  d2 = d2(2:(end)-1);
  d3 = d3(1:(end)-1);
end

% Find max in derivative as upper limit of region of interest
[max_vpp, max_vpp_idx] = max(interp_vpp(3:(end-2)));

d3 = d3(1:max_vpp_idx);
d2 = d2(1:max_vpp_idx);
d1 = d1(1:max_vpp_idx);

% Maximal d2vpp, one candidate for APthr
try 
  max_d2vpp_idx = findMax(d2, voltage_points);
  max_d2vpp_t_idx = transV2T(s, start_idx * dec_factor, max_idx, voltage_points, max_d2vpp_idx);

  % Local maximum of d3vpp, closest to max_d2vpp_idx, candidate for APthr
  max_d3vpp_idx = maxima(d3);

  if length(max_d3vpp_idx) > 0
    [sorted, sorted_idx] = sort(abs(max_d3vpp_idx - max_d2vpp_idx));
    max_d3vpp_idx = max_d3vpp_idx(sorted_idx(1));
    max_d3vpp_t_idx = transV2T(s, start_idx * dec_factor, max_idx, voltage_points, max_d3vpp_idx);
  else
    warning('calcInitVm:info', 'Cannot find local maxima in d3vpp for %s', get(s, 'id'));
  end

catch % If finding max_d2vpp_idx fails
  err = lasterror;
  if strcmp(err.identifier, 'calcInitVm:failed')
    warning('calcInitVm:info', sprintf('Error in %s: %s', get(s, 'id'), err.message));
    max_d2vpp_idx = 1;
    max_d2vpp_t_idx = 1;
    max_d3vpp_idx = 1;
    max_d3vpp_t_idx = 1;
  else
    disp(sprintf('Error in %s:', get(s, 'id')));
    rethrow(err);
  end
end

% Curvature calculation
k1 = 1 + d1 .* d1;
k = d2 ./ sqrt(k1 .* k1 .* k1);

% Maximal curvature
[max_k_idcs] = maxima(k);

% Highest maximum
[sorted, sorted_idx] = sort(k(max_k_idcs));

%sorted
%voltage_points(max_k_idcs(sorted_idx) + 2)

if length(max_k_idcs) == 0 
  error('calcInitVm:failed', 'No local maxima found for %s!', get(s, 'id'));
else
  if max_k_idcs(sorted_idx(end)) == 1	% If first point in trace is maximum
    if length(max_k_idcs) > 1
      idx = max_k_idcs(sorted_idx(end - 1));% Take next nearest maxima
    else
      error('calcInitVm:failed', 'Too few local maxima!');
    end
  else
    idx = max_k_idcs(sorted_idx(end));	% Otherwise highest maximum
  end
end

max_k_idx = idx;
max_k_t_idx = transV2T(s, start_idx * dec_factor, max_idx, voltage_points, max_k_idx);

thr_vol = voltage_points(max_k_idx + 2);

if ~ isnan(max_d2vpp_idx)

  % Find highest local maximum of k closest to max_d2vpp_idx
  distance = max_k_idcs - max_d2vpp_idx;
  [sorted, sorted_idx] = sort(distance .* distance ./ sqrt(k(max_k_idcs)));

  %sorted
  %voltage_points(max_k_idcs(sorted_idx) + 2)

  % Skip negative peaks
  max_k_near_d2vpp_idx = find(sorted >= 0);

  if length(max_k_near_d2vpp_idx) > 0
    max_k_near_d2vpp_idx = max_k_idcs(sorted_idx(max_k_near_d2vpp_idx(1)));
    max_k_near_d2vpp_t_idx = transV2T(s, start_idx * dec_factor, max_idx, voltage_points, max_k_near_d2vpp_idx);
  else
    warning('calcInitVm:info', sprintf('Cannot find nearby positive peak in d2vpp of %s. Using global max k idx instead.', get(s, 'id')));
    max_k_near_d2vpp_idx = max_k_idx;
    max_k_near_d2vpp_t_idx = max_k_t_idx;
  end
else
    max_k_near_d2vpp_idx = max_k_idx;
    max_k_near_d2vpp_t_idx = max_k_t_idx;
end

% Return all candidates
init_idx = [max_k_t_idx, max_k_near_d2vpp_t_idx, max_d2vpp_t_idx, max_d3vpp_t_idx];

s_props = get(s, 'props');

if interp_vpp(max_k_near_d2vpp_idx + 2) > s_props.init_threshold
  warning('calcInitVm:info', ['vPP curvature ignored, v'' > ' num2str(s_props.init_threshold) ' for ' ...
	   get(s, 'id')]);
  fail_cond = true(1);
else
  fail_cond = false(1);
end

if plotit
  class_name = strrep(class(s), '_', ' ');
  v = voltage_points(3:(max_vpp_idx + 2)) * 1e3;
  t = (3 : max_idx) * s.trace.dt * 1e3;
  t_data = s.trace.data(3 : max_idx);
  if isfield(s_props, 'quiet') || isfield(s.trace.props, 'quiet')
    title_str = '';
  else
    title_str = [ strrep(class(s), '_', ' ') ': ' get(s, 'id') ', ' ];
  end
  a_plot = ...
      plot_abstract({s.trace.data((3 * dec_factor): dec_factor : max_idx) * s.trace.dy * 1e3, ...
		     d1o/max(abs(d1o)), ...
		     trace_data(3 : floor(max_idx / dec_factor)) * s.trace.dy * 1e3, ...
		     d1o/max(abs(d1o)), ...
		     v, interp_vpp(3:(max_vpp_idx + 2))/max(abs(interp_vpp)), ...
		     v, d1/max(abs(d1)), v, d2/max(abs(d2)), v, d3/max(abs(d3)),...
		     v, k/max(abs(k)), '.-', ...
		     v(max_k_idx), interp_vpp(max_k_idx + 2)/max(abs(interp_vpp)), 'xr' , ...
		     v(max_k_near_d2vpp_idx), interp_vpp(max_k_near_d2vpp_idx + 2)/max(abs(interp_vpp)), '+c' , ...
		     v(max_d2vpp_idx), interp_vpp(max_d2vpp_idx + 2)/max(abs(interp_vpp)), 'dm' , ...
		     v(max_d3vpp_idx), interp_vpp(max_d3vpp_idx + 2)/max(abs(interp_vpp)), '^' }, ...
		    {'voltage [mV]', 'normalized'}, ...
		    [ title_str 'phase-plane methods, max of derivatives and curvature ' ...
		     'K_p = d^2v\prime/dv^2[1 + dv\prime/dv^2]^{-3/2}, v\prime=dv/dt' ], ...
		    {['v\prime / ' sprintf('%.2f', max(abs(d1o)))], ...
		     ['v\prime / ' sprintf('%.2f', max(abs(d1o))) ' (smooth)' ], ...
		     ['v\prime / ' sprintf('%.2f', max(abs(interp_vpp))) ' (interp)' ], ...
		     ['dv\prime/dv / ' sprintf('%.2f', max(abs(d1))) ], ...
		     [ 'd^2v\prime/dv^2 / ' sprintf('%.2f', max(abs(d2)))], ...
		     [ 'd^3v\prime/dv^3 / ' sprintf('%.2f', max(abs(d3)))], ...
		     ['K_p / ' sprintf('%.2f', max(abs(k)))], ...
		     'max K_p(v)', 'nearest max K_p(v)', ...
		     'max d^2v\prime/dv^2', 'max d^3v\prime/dv^3'}, 'plot');
  a_plot = setProp(a_plot, 'axisLimits', [v(1) v(max_vpp_idx) NaN NaN]);
end
%		     t, t_data/max(abs(t_data)), ...

% Translate from voltage to time points
function t_idx = transV2T(s, start_idx, max_idx, voltage_points, idx)
  times = find((s.trace.data((start_idx + 2) : (max_idx + 2)) * s.trace.dy) > ...
	       voltage_points(idx + 2));
  denom = s.trace.data(times(1) + start_idx + 1) - ...
      s.trace.data(times(1) + start_idx);
  % No slope? Just return the base value
  if denom == 0
    t_idx = start_idx + times(1);
  else % Otherwise, calculate rational value using linear interpolation
    t_idx = start_idx + times(1) + ...
	(voltage_points(idx + 2) / s.trace.dy - ...
	 s.trace.data(times(1) + start_idx)) / denom;
  end

function idx = findMax(data, voltage_points)
  [max_idcs] = maxima(data);
  
  % Highest maximum
  [sorted, sorted_idx] = sort(data(max_idcs));

  %sorted
  %voltage_points(max_idcs(sorted_idx) + 2)

  if length(max_idcs) == 0 
    error('calcInitVm:failed', 'No local maxima found!');
  else
    if max_idcs(sorted_idx(end)) == 1	% If maximum is the first point in trace
      if length(max_idcs) > 1
        idx = max_idcs(sorted_idx(end - 1));% Take next nearest maxima
      else
	error('calcInitVm:failed', 'Too few local maxima!');
      end
    else
      idx = max_idcs(sorted_idx(end));	% Otherwise highest maximum
    end
  end
  
