How Edge calculate current heart zone decimal value

Hi all, 

I don't understand how Garmin calculate current Heart Zone value in it's system default DataField.

I tried to develop the same "algorithm" based on my current Heart Zones but I can't get the same results...
See below an example:

Instant HR value is 62bpm. 
The simple algorithm I've made gives me I am in Zone 0.7 
Default device (I am using an Edge 840) calculation gives me Zone 0.5

My Heart Zones are the following:

Zone 0: 0 - 91
Zone 1: 92 - 109
Zone 2: 110 - 127
Zone 3: 128 - 145
Zone 4: 146 - 164
Zone 5: 165 - 183

Based on those values, my algorithm looks more precise... but I really do not understand how Garmin gives me 0.5 for the same bpm value (62).

Any idea? 

Top Replies

All Replies

  • Hmm based on my testing and your numbers, it looks like Garmin has a hardcoded assumption that your HR cannot go below 30.

    This fits with the values you see:

    [instant_hr_in_zone_0 - zone_0_min] / [zone_0_max - zone_0_min]

    = (62 - 30) / (92 - 30) 

    = 0.516 (approximately)

    When rounded to one decimal place, this value is 0.5.

    [Note: for the purposes of these calculations, zone n max is taken to be zone n+1 min. e.g. we want an instant hr of 92 to be calculated as 1.0.]

    Further anecdotal evidence:

    https://support.garmin.com/en-CA/?faq=tzwHf0B5WG61tCMtCkSfD8

    The minimum heart rate threshold for Garmin heart rate straps and Optical Heart Rate sensors to detect is 30 beats per minute.

    Does it make sense to use this minimum threshold to calculate the decimal heart rate zone? Idk, but that's what they've apparently done.

  • Thank you so much, super clear now.

    Do you think this “min” value should be applied only for zone 0 calculation?

    Or should it be applied for all the other zones?

  • No problem!

    Do you think this “min” value should be applied only for zone 0 calculation?

    Or should it be applied for all the other zones?

    Only for zone 0 calculations. You might need to select an absolute max for "zone 6" calculations: see 4a) and 4b) below.

    Here's the algorithm I might use in general, to convert an HR value to a decimal zone, in sort-of pseudocode (variable names are in bold, ← denotes assignment):

    1) hr ← hr value to be converted to a decimal zone

    2) zones ← getHeartRateZones(/* ... */);

    as per the API doc:

    The returned Array contains zone values as follows:

    • min zone 1 - The minimum heart rate threshold for zone 1

    • max zone 1 - The maximum heart rate threshold for zone 1

    • max zone 2 - The maximum heart rate threshold for zone 2

    • max zone 3 - The maximum heart rate threshold for zone 3

    • max zone 4 - The maximum heart rate threshold for zone 4

    • max zone 5 - The maximum heart rate threshold for zone 5

    Note that the implication here is max zone N = min zone N+1, which matches the way zones are configured, even if it doesn't match the way they're displayed in the "time in zone" charts

    3) min_hr ← 30

    4a) [NOTE: this is NOT what Garmin does]

    If you prefer to show a zone value > 6.0 if hr goes above zone 5 max: max_hr ← 220 (or whatever number you think should be the absolute max)

    4b) [NOTE: this is what Garmin does]

    If you prefer to show a zone value of 6.0 if hr goes above zone 5 max: max_hr ← zones[5]

    5) if hr < min_hr then hr ← min_hr

    6) if hr > max_hr then hr ← max_hr

    7) 

    min_zone[0] ← min_hr

    max_zone[0] ← min_zone[1] ← zones[0]

    max_zone[1] ← min_zone[2] ← zones[1]
    ...

    max_zone[5] ← min_zone[6] ← zones[5]

    max_zone[6] ← max_hr

    8) hr_zone ← integer zone (0-6) corresponding to hr, based on the above ranges: X is in zone Y if if min_zone[Y] ≤ X ≤ max_zone[Y]. (Note the edge case where a heart rate that's at the max of zone N / min of zone N+1 can be considered to belong to either zone, but it doesn't matter because the next step will produce the same result either way.)

    9) decimal_zone ← hr_zone +  ((1.0 * (hr - min_zone[hr_zone])) / (max_zone[hr_zone] - min_zone[hr_zone]))

    EDIT:

    Here's my algorithm in real Monkey C code:

    forums.garmin.com/.../1909282

  • Regarding the absolute max hr and "zone 6" calculations, it would be interesting to see how Garmin does this.

    When I have a chance, I might set my max HR to 120 (lowest value that can be set) and go for a run, to see how Garmin handles values over the max.

  • Again, many thanks for this super super clear explanation!

  • I did a super quick test where I set my max HR to 120, went for a 5 second run where my hr exceeded 120, and indeed Garmin’s (decimal) hr zone field displays 6.0 for any hr value greater or equal to the max HR (ie max for zone 5). 

    So in the above algorithm, if you want to match Garmin’s behaviour, you should use 4b) and not 4a)

  • So according to Garmin max zone value is 6.0, isnt' it? 

    BTW I replicate your algorithm and tried this morning on a Indoor Bike Session. 

    Almost perfect, sometimes you have Garmin's HR Zone a decimal higher, sometimes lower... probably (my wild guess) how Garmin truncate and format floats number... 

  • If the goal is to replicate the numbers that Garmin displays then ignore the rest of this comment.

    In my datafield I also display the HR and the HZ zone. The user can choose if they want to see the zone as integer or decimal.

    Regarding zone 0: I can see why the decimal value is not calculated based on the possible minimum value of 9 but rather 30. However it's kind of an arbitrary number. Of course most people won't even get close to it. A similar and somewhat more defendable choice could be to use the resting HR as a minimum for the calculation, but of course then there's a slightly bigger chance that the HR will ever get below it (and even slighter that it'll happen during an activity)

    Regarding zone 6: I do display any > 6.0 number, so in theory my user could see even 7.3, but I do not scale the gauge above 6. On the gauge they'll only see 6.

    BTW my gauge is somewhat special (well at least different from Garmin's) in that between 1 and 6 I do "stretch" the zones to be displayed linearly. I think it helps in 2 things: 1. you might easier realize that your HR zones aren't set up properly, 2. even if they are it reduces the frustration when you're not able to stay in the desired zone.

      

  • If the goal is to replicate the numbers that Garmin displays then ignore the rest of this comment.

    I assumed it was, based on this part of the OP:

    ”I don't understand how Garmin calculate current Heart Zone value in it's system default DataField.

    I tried to develop the same "algorithm" based on my current Heart Zones but I can't get the same results...”

    But as per the above discussion, I think it makes sense to:

    - understand what Garmin does

    - decide whether you (in the generic sense, not you personally) want to replicate Garmin’s behaviour

    Regarding zone 0: I can see why the decimal value is not calculated based on the possible minimum value of 9 but rather 30

    I assume you mean 0 and not 9?

    rather 30. However it's kind of an arbitrary number.

    Agreed. But it’s an arbitrary choice that kinda makes sense (but maybe not really) given that Garmin’s HR sensors won’t detect any value lower than 30. (I mean that I understand their decision even if I don’t necessarily agree with it 100%)

    A similar and somewhat more defendable choice could be to use the resting HR as a minimum for the calculation,

    That was actually my first guess but it’s not the case.

    Regarding zone 6: I do display any > 6.0 number, so in theory my user could see even 7.3, but I do not scale the gauge above 6. On the gauge they'll only see 6.

    Obviously that’s your choice, but from my pov as a technically-inclined end user, I would want to know what values > 6.0 mean, and especially what values > 7.0 mean. My best guess without knowing better is that your zone 6 ranges from zone 5 max to 220, and your zone 7 starts at 220? But I’m wondering where zone 7 ends. And whether there can be a zone 8 or zone 9.

    BTW my gauge is somewhat special (well at least different from Garmin's) in that between 1 and 6 I do "stretch" the zones to be displayed linearly. I think it helps in 2 things: 1. you might easier realize that your HR zones aren't set up properly, 2. even if they are it reduces the frustration when you're not able to stay in the desired zone.

    Interesting choice. Have you gotten any feedback about this? One possible issue that jumps out at me: what if a given zone is so narrow that it can barely be rendered on screen (or not at all)? For example, in your screenshot, I don’t see an orange segment for zone 4.

    None of my apps have an HR gauge but some of them colour-code an HR data field based on zone. In this case, I use the same colour scheme as Garmin, except as follows:

    - Garmin uses grey for both zone 0 and zone 1

    - For zone 0 I use "transparent”, and for zone 1 I use grey

    Personally I prefer to be able to visually distinguish between zone 0 and zone 1 although obviously most ppl won’t be in zone 0 for any significant amount of time during serious exercise.

  • I assume you mean 0 and not 9?

    indeed

    what values > 6.0 mean

    I don't know :) But this is my formula:

    (sorry, I just noticed a typo, the class variable should be called heartRateZones instead of mHeartRateZones)