% function states = labelPCGStates(envelope,s1_positions, s2_positions, samplingFrequency, figures) % % This function assigns the state labels to a PCG record. % This is based on ECG markers, dervied from the R peak and end-T wave locations. % %% Inputs: % envelope: The PCG recording envelope (found in getSchmidtPCGFeatures.m) % s1_positions: The locations of the R peaks (in samples) % s2_positions: The locations of the end-T waves (in samples) % samplingFrequency: The sampling frequency of the PCG recording % figures (optional): boolean variable dictating the display of figures % %% Output: % states: An array of the state label for each sample in the feature % vector. The total number of states is 4. Therefore, this is an array of % values between 1 and 4, such as: [1,1,1,1,2,2,2,3,3,3,3,4,4,4,4,4,1,1,1], % illustrating the "true" state label for each sample in the features. % State 1 = S1 sound % State 2 = systole % State 3 = S2 sound % State 4 = diastole % % This code was 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. % where a novel segmentation approach is compared to the paper by Schmidt % et al: % 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. % %% 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 states = labelPCGStates(envelope,s1_positions, s2_positions, samplingFrequency, figures) if(nargin<5) figures = false; end states = zeros(length(envelope),1); %% Timing durations from Schmidt: mean_S1 = 0.122*samplingFrequency; std_S1 = 0.022*samplingFrequency; mean_S2 = 0.092*samplingFrequency; std_S2 = 0.022*samplingFrequency; %% Setting the duration from each R-peak to (R-peak+mean_S1) as the first state: % The R-peak in the ECG coincides with the start of the S1 sound (A. G. % Tilkian and M. B. Conover, Understanding heart sounds and murmurs: with % an introduction to lung sounds, 4th ed. Saunders, 2001.) % Therefore, the duration from each R-peak to the mean_S1 sound duration % later were labelled as the "true" positions of the S1 sounds: for i = 1: length(s1_positions) %Set an upper bound, incase the window extends over the length of the %signal: upper_bound = round(min(length(states), s1_positions(i) + mean_S1)); %Set the states between the start of the R peak and the upper bound as %state 1: states(max([1,s1_positions(i)]):min([upper_bound,length(states)])) = 1; end %% Set S2 as state 3 depending on position of end T-wave peak in ECG: % The second heart sound occurs at approximately the same time as the % end-T-wave (A. G. Tilkian and M. B. Conover, Understanding heart sounds % and murmurs: with an introduction to lung sounds, 4th ed. Saunders, 2001.) % Therefore, for each end-T-wave, find the peak in the envelope around the % end-T-wave, setting a window centered on this peak as the second heart % sound state: for i = 1: length(s2_positions) %find search window of envelope: %T-end +- mean+1sd %Set upper and lower bounds, to avoid errors of searching outside size %of the signal lower_bound = max([s2_positions(i) - floor((mean_S2 + std_S2)),1]); upper_bound = min(length(states), ceil(s2_positions(i) + floor(mean_S2 + std_S2))); search_window = envelope(lower_bound:upper_bound).*(states(lower_bound:upper_bound)~=1); % Find the maximum value of the envelope in the search window: [~, S2_index] = max(search_window); %Find the actual index in the envelope of the maximum peak: %Make sure this has a max value of the length of the signal: S2_index = min(length(states),lower_bound+ S2_index-1); %Set the states to state 3, centered on the S2 peak, +- 1/2 of the %expected S2 sound duration. Again, making sure it does not try to set a %value outside of the length of the signal: upper_bound = min(length(states), ceil(S2_index +((mean_S2)/2))); states(max([ceil(S2_index - ((mean_S2)/2)),1]):upper_bound) = 3; %Set the spaces between state 3 and the next R peak as state 4: if(i<=length(s2_positions)) %We need to find the next R peak after this S2 sound %So, subtract the position of this S2 from the S1 positions diffs = (s1_positions - s2_positions(i)); %Exclude those that are negative (meaning before this S2 occured) %by setting them to infinity. They are then excluded when finding %the minumum later diffs(diffs<0) = inf; %If the array is empty, then no S1s after this S2, so set to end of %signal: if(isempty(diffs 1) if(states(first_location_of_definite_state + 1) == 1) states(1:first_location_of_definite_state) = 4; end if(states(first_location_of_definite_state + 1) == 3) states(1:first_location_of_definite_state) = 2; end end % Find the last step down: last_location_of_definite_state = find(states ~= 0, 1,'last'); if(last_location_of_definite_state > 1) if(states(last_location_of_definite_state) == 1) states(last_location_of_definite_state:end) = 2; end if(states(last_location_of_definite_state) == 3) states(last_location_of_definite_state:end) = 4; end end states(length(envelope)+1 : end) = []; %Set everywhere else as state 2: states(states == 0) = 2; %% Plotting figures if(figures) figure('Name','Envelope and labelled states'); plot(envelope); hold on; plot(states,'r'); legend('Envelope', 'States'); pause(); end