polarofficial / polar-ble-sdk

Repository includes SDK and code examples. More info https://polar.com/en/developers
Other
471 stars 154 forks source link

how to match ecg data package with HR Interval #227

Closed wilbertmatthew closed 2 years ago

wilbertmatthew commented 2 years ago

Platform your question concerns:

Device:

Description:

wilbertmatthew commented 2 years ago

I am trying to match each subscribed ECG data package returned with an RR Interval value returned in a heart rate notification. What I noticed is that in some cases I get a heart rate notification RR Interval value and then an ECG data package. But in most cases I get a heart rate notification RR Interval value and then 2-3 ECG data package events in succession. My end goal is to plot the ECG data R peaks, (largest mV value in a ECG package), and have the plot time between each R peak equal the RR Interval time. What I am seeing is that the plots of the returned ECG R data to R data time is greater than the captured RR Interval time by about 300ms. It looks like the RR Interval time is for 1 second and the ECG data is for 2 seconds.

JOikarinen commented 2 years ago

Hi @wilbertmatthew,

let see, can I help you:

The demo app can plot the ECG graph. It has recent little updates, i.e. it shows the live RR data and for clarity the x-axel vertical line steps are 1000ms ECG plot from demo app

What I am seeing is that the plots of the returned ECG R data to R data time is greater than the captured RR Interval time by about 300ms. It looks like the RR Interval time is for 1 second and the ECG data is for 2 seconds.

  • this is the part I don't fully understand. That's the reason I enhanced the Demo app a bit. As can be seen from Demo application screen shot above, when my heart rate is 60bpm (i.e. 1000ms RR interval) then from ECG graph it can be seen that each R peaks happens 1000ms apart from each other.
wilbertmatthew commented 2 years ago

Thank you for the demo update and I understand your explanation. As I understand each ECG package contains 73 data values with the same timestamp spaced 7.6 ms apart. When I debug from the time of the the first heart beat and get the RR Interval I see values in the 900 something range to 1000 something range. After getting the RR Interval value the ECG stream event fires 2-3 times in a row before the next RR Interval event. If I decode the ECG value timestamps those 2-3 ECG packages of data always have totals times greater than the RR Interval time. I send the ECG each package event data via MQTT to a browser app and plot the ECG data. I plot the ECG data and check each data value timestamp and the returned ECG data time is always greater than the RR Interval time.

  1. For your demo how many ECG packages are sent in the ECG stream event to plot the RR Interval correctly and match the RR Interval time?
  2. What is the heart rate rrs value used for?
  3. I get the ECG min, max (R peak) values on the client side to determine PQRST waves. Is there a way to find these signal values from the Polar SDK?

To recap my problem.

  1. I get an RR Interval value from the heart rate API.
  2. I get all the ECG values returned right after having gotten the RR Interval value.
  3. I plot the ECG values and decode the ECG Package timestamps so I know the total time of the ECG data packages returned before the next heart beat event.
  4. The data packages time returned in succession in the ECG stream event is always greater than the RR Interval value and thus my plot of R peak to R peak has a time span larger that the RR Interval time.
JOikarinen commented 2 years ago

Okay, if I also recap your problem, you may then confirm do I have correct understanding.

STEPS:

OBSERVATION:

JOikarinen commented 2 years ago

As I understand each ECG package contains 73 data values with the same timestamp spaced 7.6 ms apart.

  • correct, with 130Hz sampling each sample is spaced ~7.69ms apart. The number of samples in one ECG data package depends on what kind of Bluetooth connection parameters are negotiated i.e. what is the Bluetooth data packet size used on BLE communication channel (in BLE terminology the data packet size on communication is called MTU, maximum data unit). The default MTU with H10 is 232 bytes and it results each ECG package contains 73 ECG samples. Roughly it means that one ECG package contains half second of sampled data (73samples/130Hz = 0.56s). If you are using Android as you environment you have option to reduce the MTU size with API function setMtu(). If you set the MTU to minimum 70, then each ECG data packet size contains 19 samples. 19samples/130Hz = 0.146s. Please note, that MTU size can be set only on Android and setMtu() has to be called before connection is created.

After getting the RR Interval value the ECG stream event fires 2-3 times in a row before the next RR Interval event.

  • here might be something we shall discuss.
  • The ECG stream works so that when ever there is enough ECG samples sampled (i.e. the BLE transmission packet is full) the data is sent over BLE communication channel. With ECG timestamps you may accurately decide the time of the each sample on receiving side.
  • The PolarHrData given in hrNotificationReceived callback is fired around once in a second. That data is coming from BLE HR service, which is BLE standard service. BLE HR service specification says that heart value is sent around once a second. That is the reason why you may see multiple RR-values in PolarHrData. Whenever the heart is higher than 60bpm, then more than one RR value is received in PolarHrData once in a while. When the heart rate is around 120bpm (RR is 500ms) then every PolarHrData object start to list two RR values. As a summary, all the detected RR-intervals are sent over, but not exactly at the moment of detection.
  • with above explanation ECG stream and RR interval received in PolarHrData are in depend of each other. When you try to find the RR interval received in PolarHrData you found it somewhere in ECG data history.
JOikarinen commented 2 years ago

Also one important (and interesting) fact with the standard BLE HR service is that RR data received from BLE HR service is in format of 1/1024. See rrs vs rrsMs. I don't know the exact historic reason for that. Probably it is related on the MCU's used at the time specification was written, maybe MCU in heart rate sensors had some restrictions.

I recommend to use rrsMs

wilbertmatthew commented 2 years ago

As I understand each ECG package contains 73 data values with the same timestamp spaced 7.6 ms apart.

  • correct, with 130Hz sampling each sample is spaced ~7.69ms apart. The number of samples in one ECG data package depends on what kind of Bluetooth connection parameters are negotiated i.e. what is the Bluetooth data packet size used on BLE communication channel (in BLE terminology the data packet size on communication is called MTU, maximum data unit). The default MTU with H10 is 232 bytes and it results each ECG package contains 73 ECG samples. Roughly it means that one ECG package contains half second of sampled data (73samples/130Hz = 0.56s). If you are using Android as you environment you have option to reduce the MTU size with API function setMtu(). If you set the MTU to minimum 70, then each ECG data packet size contains 19 samples. 19samples/130Hz = 0.146s. Please note, that MTU size can be set only on Android and setMtu() has to be called before connection is created.

After getting the RR Interval value the ECG stream event fires 2-3 times in a row before the next RR Interval event.

  • here might be something we shall discuss.

    • The ECG stream works so that when ever there is enough ECG samples sampled (i.e. the BLE transmission packet is full) the data is sent over BLE communication channel. With ECG timestamps you may accurately decide the time of the each sample on receiving side.
    • The PolarHrData given in hrNotificationReceived callback is fired around once in a second. That data is coming from BLE HR service, which is BLE standard service. BLE HR service specification says that heart value is sent around once a second. That is the reason why you may see multiple RR-values in PolarHrData. Whenever the heart is higher than 60bpm, then more than one RR value is received in PolarHrData once in a while. When the heart rate is around 120bpm (RR is 500ms) then every PolarHrData object start to list two RR values. As a summary, all the detected RR-intervals are sent over, but not exactly at the moment of detection.
  • with above explanation ECG stream and RR interval received in PolarHrData are in depend of each other. When you try to find the RR interval received in PolarHrData you found it somewhere in ECG data history.

  • From ECG data you calculate the RR interval. Or based on previous comment I assume you measure the R-R interval from the plot drawn on browser app?

wilbertmatthew commented 2 years ago

Thanks for your response. Your Observation is correct. The last step in Steps is incorrect. I don't calculate the RR-Interval from the ECG data. I use the first value returned in the PolarHrData rrRMS field.

On Tue, Jan 25, 2022 at 3:38 AM Jukka Oikarinen @.***> wrote:

Okay, if I also recap your problem, you may then confirm do I have correct understanding.

STEPS:

OBSERVATION:

— Reply to this email directly, view it on GitHub https://github.com/polarofficial/polar-ble-sdk/issues/227#issuecomment-1020933591, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAPTOIZCH2QO52ASW4K5WDDUXZOOTANCNFSM5MPTEK4A . Triage notifications on the go with GitHub Mobile for iOS https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675 or Android https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub.

You are receiving this because you were mentioned.Message ID: @.***>

wilbertmatthew commented 2 years ago

You stated in your response.

"The number of samples in one ECG data package depends on what kind of Bluetooth connection parameters are negotiated i.e. what is the Bluetooth data packet size used on BLE communication channel (in BLE terminology the data packet size on communication is called MTU, maximum data unit). The default MTU with H10 is 232 bytes and it results each ECG package contains 73 ECG samples. Roughly it means that one ECG package contains half second of sampled data (73samples/130Hz = 0.56s). If you are using Android as you environment you have option to reduce the MTU size with API function setMtu(). If you set the MTU to minimum 70, then each ECG data packet size contains 19 samples. 19samples/130Hz = 0.146s. Please note, that MTU size can be set only on Android and setMtu() has to be called before connection is created."

I am just guessing that my problem is this.

I can confirm, for my heart, I am receiving 2-3 ECG data packages per heart beat event when my heart rate is 60 bpm. If I use your calculation of 73 ECG samples per ECG Package x 3 per heart beat I get (73 x 3)/130hz = approx 1.6 seconds between R to R peaks plotted. When I plot the ECG data on the client side app I use the returned ECG timestamps for the time value plots and the ECG data form the mV value plots (mV over time). I have a peak detector that finds the ECG data min/max peaks and use the max peak as the ECG R peak. I use the rrRMS data value returned in the MQTT data stream as the RR Interval value in ms between the two successive R peaks. When I get 3 ECG Packages I check the rrRMS value and it is always 1 ms or less. Yet when I add up the timestamps for the 3 ECG packages I get 1.6 ms of data. I have a heart condition known as Atrial Fibrillation which adds extra signals to a heart beat. Am I incorrect to use the rrRMS value as the measure of time between R peaks? Or should I use the accumulated timestamps from all ECG Packages per heart beat for the RR Interval value?

JOikarinen commented 2 years ago

Let's continue discussions. I will split your previous comment into the parts to ask more detailed questions. I roughly understand you concept, but I have problems to understand some parts. The main problem for me is that I don't get clear picture what do you mean by different terms.

I am receiving 2-3 ECG data packages per heart beat event when my heart rate is 60 bpm.

  • by "ECG data packages" I assume you mean the ECG data stream requested from H10 by call startEcgStream() . The thing I would like to highlight, is that ECG data is sampled always with 130Hz, no matter what is your current heart rate.

When I plot the ECG data on the client side app I use the returned ECG timestamps for the time value plots and the ECG data form the mV value plots (mV over time).I have a peak detector that finds the ECG data min/max peaks and use the max peak as the ECG R peak.

👍 Do you get R-R to interval from peak detector?

I use the rrRMS data value returned in the MQTT data stream as the RR Interval value in ms between the two successive R peaks.

wilbertmatthew commented 2 years ago

Thanks for your response.

You wrote: 1.

Yes I understand ECG data is always sampled at 130Hz. Yes, the ECG data stream is the data stream requested by the call startEcgStream. I included a log dump of some of the ECG Streams I received per heart beat as shown below. I process each ECG data scream in a for loop and return the data to the client app to be plotted. If you look at Section 1 below you can see that there are 4 ECG data stream packages for one heart beat in one instance. Since I am getting these 4 streams for one heart beat event I will send all 4 to the client to be plotted. The client will plot the 73 x 4 = 292 data points over time at 7.6 ms per data point for a time of 7.6ms x 292 = 2219.2 ms. The RR-Interval rsMs value returned to the client is 999ms which is way off from the wider plotted RR Interval.

I included a screenshot of the client app plotting ECG Stream data. Data points for ECG data stream labeled 0-1 shows the RR Interval returned with the stream to be 983ms. To the right of the plot is the RR Interval calculated by the app based on time between the R peaks. For the data segment RR Interval is calculated as 1015ms. Data points for ECG data stream labeled 3-4 shows the RR Interval returned with the stream to be 979ms. The RR Interval calculated by the app based on time between the R peaks. For the data segment RR Interval is calculated as 1338ms. I also included a screenshot that shows an overlay of the time plot with ECG data plotted by frequency based on a Continuous Wave Transform processing of the ECG data streams. In both cases the visual shows the varying RR Interval widths.

It seems that for one heartbeat varying amounts of ECG data is returned wherein the times between R peaks in the ECG data does not match the RR Interval time returned by the heartbeat api.

I/System.out: connectToECGDisplayBut Clicked W/ActivityThread: handleWindowVisibility: no activity for token @. @.[HRMainActivity]: MSG_WINDOW_FOCUS_CHANGED 0 1 D/InputMethodManager: prepareNavigationBarInfo() @.*** [HRMainActivity]

D/ECGActivity: Streaming feature is ready: ACC D/ECGActivity: Streaming feature is ready: ECG D/ECGActivity: Battery level 38FE9F2C 70 D/ECGActivity: Firmware: 38FE9F2C 3.1.1

D/ECGActivity: hrNotificationReceived Count 1 D/ECGActivity: ++ RR Interval from rrsMs, time, size 1003 09:09:58.020 1

D/ECGActivity: hrNotificationReceived Count 2 D/ECGActivity: ++ RR Interval from rrsMs, time, size 1000 09:09:58.957 1

D/ECGActivity: Processing ECG Stream D/ECGActivity: ** ECG Stream Package: Size, Time, Count 73 09:09:59.729 1

D/ECGActivity: hrNotificationReceived Count 3 D/ECGActivity: ++ RR Interval from rrsMs, time, size 1002 09:09:59.961 1

D/ECGActivity: hrNotificationReceived Count 4 D/ECGActivity: ++ RR Interval from rrsMs, time, size 1003 09:10:00.935 1

D/ECGActivity: Processing ECG Stream D/ECGActivity: ** ECG Stream Package: Size, Time, Count 73 09:10:01.567 1

D/ECGActivity: hrNotificationReceived Count 5 D/ECGActivity: ++ RR Interval from rrsMs, time, size 1001 09:10:01.965 1

D/ECGActivity: Processing ECG Stream D/ECGActivity: ** ECG Stream Package: Size, Time, Count 73 09:10:02.411 1

D/ECGActivity: hrNotificationReceived Count 6 D/ECGActivity: ++ RR Interval from rrsMs, time, size 1003 09:10:02.950 1

D/ECGActivity: Processing ECG Stream D/ECGActivity: ** ECG Stream Package: Size, Time, Count 73 09:10:03.006 1

D/ECGActivity: Processing ECG Stream D/ECGActivity: ** ECG Stream Package: Size, Time, Count 73 09:10:03.458 2

D/ECGActivity: hrNotificationReceived Count 7 D/ECGActivity: ++ RR Interval from rrsMs, time, size 999 09:10:03.961 1

D/ECGActivity: Processing ECG Stream D/ECGActivity: ** ECG Stream Package: Size, Time, Count 73 09:10:04.034 1

D/ECGActivity: Processing ECG Stream D/ECGActivity: ** ECG Stream Package: Size, Time, Count 73 09:10:04.576 2

D/ECGActivity: hrNotificationReceived Count 8 D/ECGActivity: ++ RR Interval from rrsMs, time, size 999 09:10:04.936 1

D/ECGActivity: Processing ECG Stream D/ECGActivity: ECG Stream Package: Size, Time, Count 73 09:10:04.972 1 D/ECGActivity: Processing ECG Stream D/ECGActivity: ECG Stream Package: Size, Time, Count 73 09:10:05.327 2 D/ECGActivity: Processing ECG Stream D/ECGActivity: ** ECG Stream Package: Size, Time, Count 73 09:10:05.649 3

D/ECGActivity: hrNotificationReceived Count 9 D/ECGActivity: ++ RR Interval from rrsMs, time, size 999 09:10:05.956 1

SECTION 1 D/ECGActivity: Processing ECG Stream D/ECGActivity: ECG Stream Package: Size, Time, Count 73 09:10:06.002 1 D/ECGActivity: Processing ECG Stream D/ECGActivity: ECG Stream Package: Size, Time, Count 73 09:10:06.232 2 D/ECGActivity: Processing ECG Stream D/ECGActivity: ECG Stream Package: Size, Time, Count 73 09:10:06.413 3 D/ECGActivity: Processing ECG Stream D/ECGActivity: ECG Stream Package: Size, Time, Count 73 09:10:06.960 4

D/ECGActivity: hrNotificationReceived Count 10 D/ECGActivity: ++ RR Interval from rrsMs, time, size 1000 09:10:06.973 2

D/ECGActivity: Processing ECG Stream D/ECGActivity: ** ECG Stream Package: Size, Time, Count 73 09:10:07.710 1

On Wed, Jan 26, 2022 at 1:06 AM Jukka Oikarinen @.***> wrote:

Let's continue discussions. I will split your previous comment into the parts to ask more detailed questions. I roughly understand you concept, but I have problems to understand some parts. The main problem for me is that I don't get clear picture what do you mean by different terms.

I am receiving 2-3 ECG data packages per heart beat event when my heart rate is 60 bpm.

When I plot the ECG data on the client side app I use the returned ECG timestamps for the time value plots and the ECG data form the mV value plots (mV over time).I have a peak detector that finds the ECG data min/max peaks and use the max peak as the ECG R peak.

👍 Do you get R-R to interval from peak detector?

I use the rrRMS data value returned in the MQTT data stream as the RR Interval value in ms between the two successive R peaks.

— Reply to this email directly, view it on GitHub https://github.com/polarofficial/polar-ble-sdk/issues/227#issuecomment-1021894396, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAPTOI7E72SREPYVJHKKGWDUX6FPZANCNFSM5MPTEK4A . Triage notifications on the go with GitHub Mobile for iOS https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675 or Android https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub.

You are receiving this because you modified the open/close state.Message ID: @.***>

JOikarinen commented 2 years ago

Thanks for clarification @wilbertmatthew. From your comments I can see you have expertise on this field and I don't see any problems in the way you use the Polar SDK.

However, I want to still highlight one aspect, which I believe is important for your implementation and may cause wrong interpretation. There is no guarantee that data from ECG stream and data from hrNotificationReceived are time wise synchronised. ECG stream does its job on 130Hz and there is somewhat predictable delay from moment of sample is taken in Polar H10 to the moment sampled data is received by your client (this delay depends on the BLE MTU and other BLE factors as discussed earlier). ECG data is, however timestamped, so you can know the exact time for each sample, if that is relevant in your application. On the other hand data received from hrNotificationReceived can be "much" older, the rrsMs can be something measured 3-4 seconds ago in PolarH10, but it reaches your application with the 3-4 seconds delay. There is no time guarantees or timestamps in data received by hrNotificationReceived.

I have a heart condition known as Atrial Fibrillation which adds extra signals to a heart beat. Am I incorrect to use the rrRMS value as the measure of time between R peaks? Or should I use the accumulated timestamps from all ECG Packages per heart beat for the RR Interval value?

  • Lets get back on your questions in earlier post. Very good questions. The ECG data is raw ECG data sampled in 130Hz, so it is up to you how to analyse the ECG data, what interpretation you may make from the ECG data. The important is that you note the fact that R-R interval (i.e. rrsMS received in hrNotificationReceived) is calculated by algorithm in PolarH10 from ECG data. That algorithm inside PolarH10 tries its best to detect R peaks to measure the R-R intervals. If the user has some symptoms like Atrial Fibrillation, the algorithm may produce the wrong R-R interval results as the algorithm is tuned for predicted behaviour of the heart.

This might be also something you may be interested to read: https://www.polar.com/sites/default/files/static/science/white-papers/polar-h10-heart-rate-sensor-white-paper.pdf

wilbertmatthew commented 2 years ago

Thank you for all your help in clarifying how the Polar 10 ECG data capture works. It seems now I should be calculating the RR Interval by using my peak detector. I know the peak detector works correctly on test data provided by the MIT BIH database. I have tested it on regular and irregular heart beats provided by that database.

On Thu, Jan 27, 2022 at 2:07 AM Jukka Oikarinen @.***> wrote:

Thanks for clarification @wilbertmatthew https://github.com/wilbertmatthew. From your comments I can see you have expertise on this field and I don't see any problems in the way you use the Polar SDK.

However, I want to still highlight one aspect, which I believe is important for your implementation and may cause wrong interpretation. There is no guarantee that data from ECG stream and data from hrNotificationReceived are time wise synchronised. ECG stream does its job on 130Hz and there is somewhat predictable delay from moment of sample is taken in Polar H10 to the moment sampled data is received by your client (this delay depends on the BLE MTU and other BLE factors as discussed earlier). ECG data is, however timestamped, so you can know the exact time for each sample, if that is relevant in your application. On the other hand data received from hrNotificationReceived can be "much" older, the rrsMs can be something measured 3-4 seconds ago in PolarH10, but it reaches your application with the 3-4 seconds delay. There is no time guarantees or timestamps in data received by hrNotificationReceived.

I have a heart condition known as Atrial Fibrillation which adds extra signals to a heart beat. Am I incorrect to use the rrRMS value as the measure of time between R peaks? Or should I use the accumulated timestamps from all ECG Packages per heart beat for the RR Interval value?

  • Lets get back on your questions in earlier post. Very good questions. The ECG data is raw ECG data sampled in 130Hz, so it is up to you how to analyse the ECG data, what interpretation you may make from the ECG data. The important is that you note the fact that R-R interval (i.e. rrsMS received in hrNotificationReceived) is calculated by algorithm in PolarH10 from ECG data. That algorithm inside PolarH10 tries its best to detect R peaks to measure the R-R intervals. If the user has some symptoms like Atrial Fibrillation, the algorithm may produce the wrong R-R interval results as the algorithm is tuned for predicted behaviour of the heart.

This might be also something you may be interested to read:

https://www.polar.com/sites/default/files/static/science/white-papers/polar-h10-heart-rate-sensor-white-paper.pdf

— Reply to this email directly, view it on GitHub https://github.com/polarofficial/polar-ble-sdk/issues/227#issuecomment-1022909405, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAPTOI35NUDCRZIVOQ4SJ23UYDVLZANCNFSM5MPTEK4A . Triage notifications on the go with GitHub Mobile for iOS https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675 or Android https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub.

You are receiving this because you were mentioned.Message ID: @.***>

JOikarinen commented 2 years ago

@wilbertmatthew no problem, don't hesitate to ask more if needed.

wilbertmatthew commented 2 years ago

Thanks. One other question. Is the code that processes the ECG R peaks and QRS complexes available? There are a number of peak detection algorithms. Can you tell me which one is used such as Pan Tompkins, CWT, etc.

On Thu, Jan 27, 2022 at 8:19 AM Jukka Oikarinen @.***> wrote:

@wilbertmatthew https://github.com/wilbertmatthew no problem, don't hesitate to ask more if needed.

— Reply to this email directly, view it on GitHub https://github.com/polarofficial/polar-ble-sdk/issues/227#issuecomment-1023201765, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAPTOI3Y4NJK5MHAFOS46D3UYFA5XANCNFSM5MPTEK4A . Triage notifications on the go with GitHub Mobile for iOS https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675 or Android https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub.

You are receiving this because you were mentioned.Message ID: @.***>

JOikarinen commented 2 years ago

very good question. Unfortunately, from Polar side there is no code for processing ECG R peaks or QRS complexes available.