% function [despiked_signal] = schmidt_spike_removal(original_signal, fs)
%
% This function removes the spikes in a signal as done by Schmidt et al in
% the paper:
% Schmidt, S. E., Holst-Hansen, C., Graff, C., Toft, E., & Struijk, J. J.
% (2010). Segmentation of heart sound recordings by a duration-dependent
% hidden Markov model. Physiological Measurement, 31(4), 513-29.
%
% The spike removal process works as follows:
% (1) The recording is divided into 500 ms windows.
% (2) The maximum absolute amplitude (MAA) in each window is found.
% (3) If at least one MAA exceeds three times the median value of the MAA's,
% the following steps were carried out. If not continue to point 4.
% (a) The window with the highest MAA was chosen.
% (b) In the chosen window, the location of the MAA point was identified as the top of the noise spike.
% (c) The beginning of the noise spike was defined as the last zero-crossing point before theMAA point.
% (d) The end of the spike was defined as the first zero-crossing point after the maximum point.
% (e) The defined noise spike was replaced by zeroes.
% (f) Resume at step 2.
% (4) Procedure completed.
%
%% Inputs:
% original_signal: The original (1D) audio signal array
% fs: the sampling frequency (Hz)
%
%% Outputs:
% despiked_signal: the audio signal with any spikes removed.
%
% This code is derived from the paper:
% S. E. Schmidt et al., "Segmentation of heart sound recordings by a
% duration-dependent hidden Markov model," Physiol. Meas., vol. 31,
% no. 4, pp. 513-29, Apr. 2010.
%
% Developed by David Springer for comparison purposes in the paper:
% D. Springer et al., ?Logistic Regression-HSMM-based Heart Sound
% Segmentation,? IEEE Trans. Biomed. Eng., In Press, 2015.
%
%% Copyright (C) 2016 David Springer
% dave.springer@gmail.com
%
% 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
% 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 .
function [despiked_signal] = schmidt_spike_removal(original_signal, fs)
%% Find the window size
% (500 ms)
windowsize = round(fs/2);
%% Find any samples outside of a integer number of windows:
trailingsamples = mod(length(original_signal), windowsize);
%% Reshape the signal into a number of windows:
sampleframes = reshape( original_signal(1:end-trailingsamples), windowsize, []);
%% Find the MAAs:
MAAs = max(abs(sampleframes));
% While there are still samples greater than 3* the median value of the
% MAAs, then remove those spikes:
while(~isempty(find((MAAs>median(MAAs)*3))))
%Find the window with the max MAA:
[val window_num] = max(MAAs);
if(numel(window_num)>1)
window_num = window_num(1);
end
%Find the postion of the spike within that window:
[val spike_position] = max(abs(sampleframes(:,window_num)));
if(numel(spike_position)>1)
spike_position = spike_position(1);
end
% Finding zero crossings (where there may not be actual 0 values, just a change from positive to negative):
zero_crossings = [abs(diff(sign(sampleframes(:,window_num))))>1; 0];
%Find the start of the spike, finding the last zero crossing before
%spike position. If that is empty, take the start of the window:
spike_start = max([1 find(zero_crossings(1:spike_position),1,'last')]);
%Find the end of the spike, finding the first zero crossing after
%spike position. If that is empty, take the end of the window:
zero_crossings(1:spike_position) = 0;
spike_end = min([(find(zero_crossings,1,'first')) windowsize]);
%Set to Zero
sampleframes(spike_start:spike_end,window_num) = 0.0001;
%Recaclulate MAAs
MAAs = max(abs(sampleframes));
end
despiked_signal = reshape(sampleframes, [],1);
% Add the trailing samples back to the signal:
despiked_signal = [despiked_signal; original_signal(length(despiked_signal)+1:end)];