function sector_perf = eval_sector_perf( result_mat, gt_mat, verbose )
  
% FUNCTION SECTOR_PERF = EVAL_SECTOR_PERF( RESULT_MAT, GT_MAT, [ VERBOSE ] )
%
% (1) On active frames only: for each active source instance compute TP,FN and recall.
% (2) On all frames: sum over all instances AND over silence -> TP,FP,TN,FN and derived measures.
% 
% See also STATIC_GT_2_SECTOR_GT (includes more explanations).
%
% RESULT_MAT: N_sectors - by - N_frames matrix of zeros and ones.
%
% GT_MAT: N_instances - by - 1 cell array of N_sectors - by - N_frames matrices of integer values (either zero or object id). Non-zero values locate in both space and time the activity of a given active source instance. The value itself gives the identity of the object.
%
% Note: we use a cell array of 2D matrices instead of a 3D matrix 
% because sparse matrices cannot be more than 2-dimensional.
%
% VERBOSE:  optional parameter, 0 or 1 (default value is 1).
%
% "GT" means Ground-Truth
% 
% Note that a given object may be represented by several "active source instances" in the GT,
% e.g. one instance for each GT speech segment of a given speaker.
%
% OUTPUT: a structure containing various evaluation results.
%
% Typical use: some black box search method outputs a binary matrix (e.g. thresholding on a matrix of SAM values). We want to evaluate the result 1) overall 2) for each active source instance. We also want to determine how well multiple concurrent sources can be localized (histograms).

  if nargin < 2
    error( 'eval_sector_perf needs at least two input parameters' );
  end
  
  % ------------------------------------------------------------
  % ( 1 ) Check input parameters
  
  siz1 = size( result_mat );
  if length( siz1 ) ~= 2
    error( 'eval_sector_perf: "result_mat" must be a 2-dimensional matrix' );
  end
  
  if ~iscell( gt_mat )
    error( 'eval_sector_perf: "gt_mat" must be a CELL ARRAY of 2-dimensional matrices.' );
  end

  % How many active source instances ?
  
  N_instances = length( gt_mat );
  
  for a = 1:N_instances
    
    siz2 = size( gt_mat{ a } );
    
    if length( siz2 ) ~= 2
      error( sprintf( 'eval_sector_perf: "gt_mat{%d}" must be a 2-dimensional matrix', a ) );
    end
    
    if ~all( siz1 == siz2 )
      error( sprintf( 'eval_sector_perf: "result_mat" and "gt_mat{%d}" must have same size', a ) );
    end
    
  end
  
  if ~exist( 'verbose', 'var' )
    verbose = 1;
  end
  
  if ~ismember( verbose, [0 1] )
    error( 'eval_sector_perf: "verbose" must be 0 or 1' );
  end
  
  % How many sectors in space ? 
    
  N_sectors = siz2( 1 );
  
  % How many time frames ?
  
  N_frames  = siz2( 2 );
  
  % Reduce memory usage
  
  if ~issparse( result_mat )
    result_mat = sparse( result_mat );
  end
  
  for a = 1:N_instances
    if ~issparse( gt_mat{ a } )
      gt_mat{ a } = sparse( gt_mat{ a } );
    end
  end
    
  % Verbosity
  
  if verbose
    disp( sprintf( 'eval_sector_perf:  N_sectors = %d,  N_frames = %d,  N_instances = %d', N_sectors, N_frames, N_instances ) );
  end
  
  % ------------------------------------------------------------
  % ( 2 ) Store input parameters
  
  sector_perf.result_mat = result_mat;
  
  % We don't store the GT because it is usually quite big
  
  sector_perf.N_sectors   = N_sectors;
  sector_perf.N_frames    = N_frames;
  sector_perf.N_instances = N_instances;
  
  % -------------------------------------------------------------
  % ( 3 ) Evaluate and store results 
  % for each active source instance separately

  for a = 1:sector_perf.N_instances

    % Where we'll store everything about this instance
    an_instance = [];
    
    tmp = gt_mat{a};
    an_instance.object_id = unique( tmp( find( tmp ) ) );

    if length( an_instance.object_id ) < 1
      error( sprintf( 'eval_sector_perf: empty instance #%d', a ) );
    end
    
    if length( an_instance.object_id ) > 1
      error( sprintf( 'eval_sector_perf: multiple object ids for instance #%d', a ) );
    end
    
    % ( 3.1 ) First look at the GT itself
    
    [ ind_sector, ind_frame ] = find( gt_mat{ a } );
     
    % In the GT, list the frames occupied by this active source instance
    an_instance.frame_list  = sort( unique( ind_frame ) );
    an_instance.frame_list  = an_instance.frame_list(:).';
    an_instance.n_frames    = length( an_instance.frame_list );
    
    % In the GT, list the (sector,time frame) occupied by this active source instance
    % -> note that there may be more than one sector per time frame
    %    typically when the true location of an active source 
    %    is at the border between two sectors
    an_instance.sector_list = sub2ind( size( gt_mat{ a } ), ind_sector, ind_frame );
    an_instance.sector_list = an_instance.sector_list(:).';
    
    % ( 3.2 ) Second, compute results
    
    % "result_per_frame": vector of "an_instance.n_frames" binary values
    % 0 means active source not detected
    % 1 means active source detected
    %
    % Note that these 3 lines of code automatically take care of 
    % the borderline cases (equivalent to an OR operator),
    % i.e. when the true location of an active source instance is
    % in more than one sector.
    
    m = reshape( gt_mat{ a }( an_instance.sector_list ) & result_mat( an_instance.sector_list ), length( unique( ind_sector ) ), [] );
    
    an_instance.result_per_frame = any( m, 1 );
    
    % ( 3.3 ) Finally, some statistics
    % true positives, false negatives, and recall
    
    an_instance.TP = sum( an_instance.result_per_frame );
    an_instance.FN = sum( ~an_instance.result_per_frame );
    an_instance.recall = an_instance.TP / ( an_instance.TP + an_instance.FN );
    
    % ( 3.4 ) Store the new object

    sector_perf.instance_list( a ) = an_instance;
    
  end
  
  if verbose
    disp( 'eval_sector_perf: finished evaluating each active source instance' );
  end
  
  % ------------------------------------------------------------
  % ( 4 ) Overall results
  
  sector_perf.TP = sum( [ sector_perf.instance_list.TP ] );
  sector_perf.FN = sum( [ sector_perf.instance_list.FN ] );
  
  % Look at (sector,time frame) pairs NOT occupied by any active source instance
  
  occupied = sparse( N_sectors, N_frames );
  for a = 1:N_instances
    occupied = occupied | gt_mat{ a };
  end
  
  sector_perf.TN = sum( reshape( ~occupied & ~result_mat, 1, [] ) );
  sector_perf.FP = sum( reshape( ~occupied &  result_mat, 1, [] ) );
  
  [ far,  frr,  hter ] = far_frr( sector_perf.TP, sector_perf.FP, sector_perf.TN, sector_perf.FN );
  
  sector_perf.far = full( far );
  sector_perf.frr = full( frr );
  sector_perf.hter = full( hter );

  [ prc, rcl, F ] = prc_rcl( sector_perf.TP, sector_perf.FP, sector_perf.TN, sector_perf.FN );
  
  sector_perf.prc = full( prc );
  sector_perf.rcl = full( rcl );
  sector_perf.F   = full( F );
  
  % Average instance recall
  
  x = [ sector_perf.instance_list.recall ];
  w = [ sector_perf.instance_list.n_frames ];
  sector_perf.mean_instance_recall = sum( x .* w ) / sum( w );
  
  % Minimum instance recall
  
  x = [ sector_perf.instance_list.recall ];
  sector_perf.min_instance_recall = min( x );
  
  
  if verbose
    disp( 'eval_sector_perf: fininshed evaluating overall results' );
  end

  % ------------------------------------------------------------
  % ( 5 ) Results broken down per number of active sources
  %       -> histograms.
  
  % ( 5.1 ) For each frame, compute the number of active sources
  % in the GT and in the result.
  % "sources in the results" means sources correctly localized.
  
  sector_perf.gt_n_active = zeros( 1, N_frames );
  sector_perf.result_n_active = zeros( 1, N_frames );
  
  for a = 1:sector_perf.N_instances
    
    % Look at the GT
    frame_list = sector_perf.instance_list( a ).frame_list;
    sector_perf.gt_n_active( frame_list ) = sector_perf.gt_n_active( frame_list ) + 1;
    
    % Look at the result
    sector_perf.result_n_active( frame_list ) = sector_perf.result_n_active( frame_list ) + sector_perf.instance_list( a ).result_per_frame;
    
  end
  
  % ( 5.2 ) Compute the histogram of number of sources found 
  % for each TRUE number of active sources
  
  sector_perf.gt_n_active_list = sort( setdiff( unique( sector_perf.gt_n_active ), [0] ) );
  
  sector_perf.histo_multisource = cell( max( sector_perf.gt_n_active_list ), 1 );
  
  for a = sector_perf.gt_n_active_list
    
    x = 0:a;
    
    frame_list = find( sector_perf.gt_n_active == a );
    
    n = hist( sector_perf.result_n_active( frame_list ), x );
    
    sector_perf.histo_multisource{ a } = n;
    
  end
  
  if verbose
    disp( 'eval_sector_perf: finished computing histograms.' );
    disp( 'eval_sector_perf: done.' );
  end
  