THE AUDITORY MODELING TOOLBOX

Applies to version: 1.1.0

View the help

Go to function

MCLACHLAN2021_METRICS - - extract localization metrics

Program code:

function metric = mclachlan2021_metrics(doa)
%MCLACHLAN2021_METRICS - extract localization metrics
%   Usage: metric = mclachlan2021_metrics(doa) 
%
%   Input parameters:
%
%   doa                 : Struct returned from the mclachlan2021 model with
%                         estimated and real directions of arrival
%
%   Output parameters:
%
%     metric            : metrics for evaluation of localisation
%                         performance
%
%       .reversal_fb    : Percentage of front-back reversals, omitting
%                         small errors occurring within 10 degrees of the
%                         plane dividing the hemifields
%       .reversal_ud    : Percentage of up-down reversals, omitting
%                         small errors occurring within 10 degrees of the
%                         plane dividing the hemifields
%       .rmsL           : Lateral root mean squared error
%       .rmsP           : Polar root mean squared error, omitting reversals
%                         and restricted to directions within 35 degrees of
%                         the vertical midline
%
%
%   MCLACHLAN2021_METRICS(...) returns psychoacoustic performance 
%   parameters for experimental response patterns. 
%   doa is a struct where actual and estimated directions of arrival must
%   be provided.
%   
%   See also: demo_mclachlan2021 mclachlan2021
%
%   References:
%     D. Schonstein, L. Ferre, and B. F. G. Katz. Comparison of headphones
%     and equalization for virtual auditory source localization. Proc.
%     Euronoise, 2008.
%     
%     J. Reijniers, D. Vanderleist, C. Jin, C. S., and H. Peremans. An
%     ideal-observer model of human sound localization. Biological
%     Cybernetics, 108:169--181, 2014.
%     
%
%   Url: http://amtoolbox.org/amt-1.1.0/doc/modelstages/mclachlan2021_metrics.php

% Copyright (C) 2009-2021 Piotr Majdak, Clara Hollomey, and the AMT team.
% This file is part of Auditory Modeling Toolbox (AMT) version 1.1.0
%
% This program is free software: you can redistribute it and/or modify
% it under the terms of the GNU General Public License as published by
% the Free Software Foundation, either version 3 of the License, or
% (at your option) any later version.
%
% This program 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 General Public License for more details.
%
% You should have received a copy of the GNU General Public License
% along with this program.  If not, see <http://www.gnu.org/licenses/>.

%   AUTHOR: Glen McLachlan (adapted from code provided by Michael Sattler and Roberto Barumerli)

% real doa, in spherical and lat-pol coordinates
sph_real = SOFAconvertCoordinates(doa.real, 'cartesian', 'spherical');
[lat_real,pol_real]=sph2horpolar(sph_real(:,1),sph_real(:,2));

doa.est = squeeze(doa.est);

if size(doa.est, 3) ~= 1 % remove third dimension relative to repetition of experiments
    num_exp = size(doa.est, 2);
    doa_est = [];

    for i = 1:size(doa.est, 2)
        est = squeeze(doa.est(:,i,:));
        doa_est = [doa_est; est];
    end
else
    if(size(doa.est, 1) == num_dirs)
        num_exp = 1;
    else
        num_exp = size(doa.est, 1);
    end
    doa_est = doa.est;
end

% mean estimated doa over all experiments, in spherical and lat-pol coordinates
sph_est = SOFAconvertCoordinates(doa_est, 'cartesian', 'spherical');
[lat_est,pol_est] = sph2horpolar(sph_est(:,1),sph_est(:,2));

% matrix of all doas
m = zeros(size(sph_real, 1)*num_exp, 14);
m(:, 1:2) = repmat(sph_real(:, [1 2]), num_exp, 1);     % spherical real
m(:, 3:4) = sph_est(:, [1 2]);                          % spherical estimate
m(:, 5:6) = repmat([lat_real,pol_real], num_exp, 1);    % lat-pol real
m(:, 7:8) = [lat_est,pol_est];                          % lat-pol estimate
m(:, 9:11) = repmat(doa.real(:,1:3),num_exp, 1);        % cartesian real
m(:, 12:14) = doa_est(:,1:3);                           % cartesian estimate

% ensure real values, as sph2horpolar can return complex numbers 
% due to numerical approximations
m = real(m);

num_dirs = size(doa.real,1);

%% metrics

% lateral RMS
metric.rmsL=sqrt(sum((mynpi2pi(m(:,7)-m(:,5))).^2)/num_dirs);

% percentage of quadrant errors according to Schonstein et al. (2008)
idx_fb = find(acos(sum(m(:,9:11).*m(:,12:14),2))>... % fb reversals
    acos(sum([-m(:,9) m(:,10) m(:,11)].*m(:,12:14),2))...
    & (abs(m(:,6))<80 | (m(:,6)>100 & m(:,6)<260))); % drop dirs near midline

idx_ud = find(acos(sum(m(:,9:11).*m(:,12:14),2))>... % ud reversals
    acos(sum([m(:,9) m(:,10) -m(:,11)].*m(:,12:14),2))...
    & ((abs(m(:,6))>10 & m(:,6)<170)| m(:,6)>190)); % drop dirs near midline

idx = find(abs(m(:,5))<=35); % ignore 10 degrees around interaural axis
idx_fbm = intersect(idx,idx_fb);
idx_udm = intersect(idx,idx_ud); 

metric.reversal_fb = size(idx_fbm,1)*100/num_dirs; %percent fb reversals
metric.reversal_ud = size(idx_udm,1)*100/num_dirs; %percent ud reversals

% polar RMS excluding quadrant errors and 10 degrees around interaural axis
m_rmsP = m;
m_rmsP([idx_fb; idx_ud],:) = [];
idx = abs(m_rmsP(:,5))<=35; % ignore 10 degrees around interaural axis
m_rmsP = m_rmsP(idx,:);

metric.rmsP=sqrt(sum((mynpi2pi(m_rmsP(:,8)-m_rmsP(:,6))).^2)/size(m_rmsP,1));

%% PLOTS
pol_err = abs(mynpi2pi(m(:,8)-m(:,6))); % plottable polar error
lat_err = abs(mynpi2pi(m(:,7)-m(:,5))); % plottable lateral error

% plottable data, omitting dirs near midline
p_err = sum(reshape(pol_err,num_dirs,num_exp),2)/num_exp;
l_err = sum(reshape(lat_err,num_dirs,num_exp),2)/num_exp;
n_fb = zeros(size(m,1),1);
n_ud = zeros(size(m,1),1);
n_fb(idx_fb) =  1;
n_ud(idx_ud) =  1;
n_fb = sum(reshape(n_fb,num_dirs,num_exp),2)*100/num_exp; % front-back
n_ud = sum(reshape(n_ud,num_dirs,num_exp),2)*100/num_exp; % up-down

plot_reijniers2014(doa.real,l_err); % plot lateral error
title('Lateral error in [deg]');
plot_reijniers2014(doa.real,p_err); % plot polar
title('Polar error in [deg]');
caxis([0, 50]);
plot_reijniers2014(doa.real,n_fb); % plot fb errors
title('Percentage of front-back reversals');
caxis([0 50]);
plot_reijniers2014(doa.real,n_ud); % plot ud errors
title('Percentage of up-down reversals');
caxis([0 50]);

end

function out_deg=mynpi2pi(ang_deg)
ang=ang_deg/180*pi;
out_rad=sign(ang).*((abs(ang)/pi)-2*ceil(((abs(ang)/pi)-1)/2))*pi;
out_deg=out_rad*180/pi;
end