Tuesday, April 23, 2019

Sensor value comparison, BSX, Hex, Moxy

Published studies have evaluated the accuracy of the 3 main muscle O2 sensors, but how do they compare in side by side?  If you only have one brand of sensor, this may not be important, however if you have been using a BSX unit and switch to the Hex, will your historic values hold up?  In addition, let's say all 3 are "accurate" in tracking O2 changes, but you want to buy the one with the best dynamic range.  Which one has that property?

I personally have 3 BSX and 1 Hex sensors and was also able to get some data comparing a Moxy to the Hex. This post will be about some observations on all three.  Several issues to consider when making comparisons are the following:
  • Location of each device - The baseline and range of O2 sat will of course be different between muscle groups.  However, the location within the same muscle will also yield different values.  The proximal vs distal site of the quadriceps should be different.  Therefore if we are using two sensors on the RF for example, they may not have the same dynamic range simply because of this factor.
  • Left vs Right legs - Some individuals have leg strength and/or O2 saturation asymmetry.  In my case, my L RF has a much higher dynamic range of saturation values despite having near 50/50 percent power equality.  So, a direct left vs right comparison will not be valid.
  • Light interference -  I noticed that on some occasions the BSX strobe LED caused artifacts in the Hex readings.  The fix is to place them at least a few cm apart, but then you are getting the proximal vs distal difference effect again. 
What I have done in the cycling interval tracings below is a proximal/distal placement on one day to get a general idea of pattern, then moving the Hex to exactly where the BSX was on the L RF on a different day but doing a similar interval (Wingate 60).  The near exact placement location and similar power interval should give us a good idea on dynamic range difference.

Proximal BSX, distal Hex on the L RF - 350 watt x 3 min with a cool down:
The sensors are on the same rectus femoris but one on top of the other.  Pattern should be matched on the time scale.

O2 sat

Total Hb:


  • The pattern of O2 saturation, deoxy hemoglobin appear very similar.
  • Dynamic range of the Hex is markedly less, but this could be related to distal position.
  • There may be a slight difference in THb.

Wingate 60 at 500 watts:
Same day, placement

O2 sat 

 Total Hb


  • The pattern of O2 saturation, deoxy hemoglobin appear very similar.
  • Dynamic range of the Hex is markedly less (55 vs 20%), but this could be related to distal position.
  • The difference in THb is minimal in this max interval session.

Different day, but now the Hex is in the same spot as the BSX sensor was in the tracings above.  
Perhaps the dynamic range will be better?

Wingate 60 at 500w with cool down - Hex only - O2 sat:



  • The pattern and dynamic range of O2 sat and HHb are now a bit closer to the BSX unit.
  • It seems that location does make a big difference in this regards.  
  • On the R RF (not shown) the BSX O2 sat dropped from a baseline of 70 to about 50%.  
  • The total Hb pattern resembles the BSX (in the same location) closely.

Data line up: What I have done here is to take the day of left RF sensor data from the BSX and match it in time to the different day of Hex left RF data (same spot).  The interval was within 1-2% of the same power average and I show both power curves:

Summary of same location, same power profile:

                                Net range
BSX  70 to 15%            55%
Hex   64 to 30%            34%

  • Even with the same placement, the Hex has a more limited dynamic range than the BSX.
  • In my case, a distal vs proximal placement as well as left vs right leg location does impact the result but not the pattern of saturation, total Hb change.

How does the Hex do on a weight training session?
This is a comparison between the Hex and BSX on my L tricep doing body weight dips.  The length of the active interval is about 30-40 seconds.



 Summary of same location, same power profile:

                                Net range
BSX  66 to 1%             65%
Hex   60 to 7%             53%

  • The BSX has a higher min to max range.
  • Start and stop changes are blunted in the Hex due to the time averaging algorithm they employ.
  • Although the Hex has a smaller dynamic range, it is still ample and one could argue that avoiding near zero values is an advantage.  

Moxy vs Hex
Although I don't own a Moxy sensor, I am using data from my friend the XCSkier who does.
The following tracings are from a session of cross country skiing, Moxy on the R RF and the Hex on the L RF.  Although mirror image location was the same, we can't directly compare legs based only this data.  If we had 2 Moxy's or 2 of the same brand that were documented to match, that would be a different situation.  But we can get an idea of trends and will look at range anyway.

Up and down hill.
O2 sat:
  • Both the Moxy and Hex track in a very similar fashion.  This is certainly good to see if you are considering a switch from one to the other.
  • The range of the Moxy is larger than the Hex, but since they are on different legs, it is hard to say if this is based on location or sensor design.

Total Hemoglobin:

  • The tracking of total hemoglobin in a short time frame is not as evenly matched.  Whether this is from the smoothing algorithm on the Hex is unclear.

To better examine the total Hb change, I decided to look for reperfusion changes during a segment of low heart rate, hoping this corresponded to coasting:

Here is the Moxy O2 sat and total Hb.  
I circled in blue where heart rate drops (light gray) and the total Hb, O2 sat markedly rise.

The Hex during the same segment:
  • The first circled area exhibits the same behavior with rapid O2 sat increase and total Hb.
  • However, the base of the total Hb tracing is broader, possibly from smoothing.
  • On the second marked area there is still agreement, but not as much.  My guess is this is also related to the smoothing done on the Hex.

  • BSX, Moxy and Hex sensors all track O2 saturation and total hemoglobin in an overall similar fashion.
  • The dynamic range of the Hex is less than the BSX and probably less than the Moxy.  This can be troublesome in a muscle location that already has a marginal resting to nadir value range.
  • There is a substantial smoothing algorithm employed in the Hex that can mask sharp change in data values.  In my opinion, this should be an optional feature and potentially interfere with interpretation of the sensor's data.  I would personally rather have it off and if I needed smoothing, just design that into the spreadsheet after data extraction.
  • Different legs can have very different saturation ranges despite identical location, it can't be assumed they will match unless tested.
  • As long as these factors are considered, all three devices should be interchangeable for desaturation patterns, making them all useful for MLSS measurement in long intervals.

Sunday, April 14, 2019

Data Presentation and Analysis - Plotting

The next step once we have a solid spreadsheet of our data is to graph and analyze it.  If you have a background in stats or engineering, your knowledge base is vastly superior to mine so ignore this post.  This is intended as a simple, no cost way of data presentation for users with little or no data manipulation background.

A graphing web app by the name of Plot.ly is my personal favorite.  If you like to use a dedicated graphics or data package that may work even better.

You will need to sign up for a free account, then we are ready to begin.

Time, Power, Heart rate graph for the entire session:
The first step is to import the spreadsheet we just created.  Click the Import button to the right (in red):

You will get this dialog:

 Upload the spreadsheet (takes a few seconds).  You will then have this:

As you can see, all our data is there with the headers on top.  To plot a single column, click "+ Trace" (next to red arrow above).

The following will appear:
 We want to select the line graph (above red arrow).

The next step is to tell the app what X and Y values to graph.  The X has a pull down list, select "Time" and the Y should be the "power".  In the future, the X is usually Time (the same value on the list, time is the same for all sensors), and the Y will be each sensor data range we want to graph.
Notice we now have a graph in purple of watts.

Let's add heart rate to the curve.  Click the "+Trace" again to add another sensor series:
The X axis is again the time, the Y is heart rate.  However, we can have a separate scaling on the Y (a second Y axis).  If you click the "+" button next to the Y axis, a second range is created (far right side).

Here is the final result so far:
Not very pretty or readable but we can clean this up:
First, lets add text to the axis labels, title and trace labels (top R).  The trace labels list can also be moved around.  To change the labels just click on them and type some appropriate text.

To boost the font size, press the "Style" button on the left side of the screen:
Here I darkened the font color and changed the size so we can read it.

Although it is possible to zoom and analyze the plot, I prefer to copy out a section of the original master spreadsheet and load it independently.   The web app is just not robust enough to handle over 100,000 data points.

Go back to openoffice, open the original sheet and find the segment of interest.  For the above, I wanted the 5000 to 6000 second range:
I will then click to first box of interest on the far left, scroll down to the end of range I want and shift click on the far right (last data series) to select the whole list of values

Then control C to copy.

Go to menu, open a new sheet, then paste into the second row:

You can then go back to the original sheet to copy the first row title headers and paste into the new sheet:

Save it with an appropriate name, go back to the Plot.ly app and import it.

Now we will do a full graph of SmO2, power, heart rate and ventilation for this training interval.

What I have done here is to get back to where we were above with power/HR over time and added Ventilation (using the second Y axis for HR, far right side):

Let's now look at a couple of SmO2 fields (RF and Costal HHb):
The same Y axis was used for watts and ventilation and the right Y axis for HHb.

Here I dimmed out the Watts for a better look (using the Style tab on the far left toolbar.

Some more color changes to put Ventilation in the background (in yellow).
This is good for a final.  

To save the image as a jpeg, press the export button (red arrow below):

Here is the final:
  • At this point if you are satisfied, a screen shot copy, or export as jpeg can get a high res image.  One could also save this in the web app (the blue button on the far left).

Curve fitting:
The web app has some primitive curve fitting which is nice if you want a line drawn through your data objectively.
  • Why would you need to do so?  For example - looking for MLSS on a long interval.  Using either SmO2 (RF/costal) or Ventilation rate over a > 5 minute constant power interval can be used to determine this.  Having an accurate regression line with slope may help determine which power level causes a take off in slope.
Under Analysis (left menus), choose "Curve fitting" and select a Trace (I did Ventilation) on the top drop down.  You can only curve fit a data series if it is using the left Y axis.
Enable advanced to get the calculation range to show (X min and X max range of values).

We need to populate the X min max fields (both the calculation and display range).  If you hover over the line trace data you will see the X values on the curve as well:
Put in a 0 placeholder in the M and X fields.
I put in 5600 to 5800 seconds for the Ranges.  Make sure the "matching data" box is in blue.

Then press the Run button
You should have a curve fit line just through the range of data selected:

I took the liberty of running curve fits for both costal and RF HHb.

Both the Hexoskin web app as well as Garmin do not allow for easy calculation of average values.  
  • For example, let's say you were looking at your long MLSS power interval again and wanted to know at what average SmO2 or Vent rate this was attained.  Even though you may know the power figure, there is no way of getting the corresponding Vent rate average from the Hexoskin site, or the SmO2 average from Garmin.
Although I'm not going to be getting into spreadsheet calculation detail, lets at least see how to get an average for a range.   We are going to get the average power and vent rate for the second interval on the sheet.
Here is a section of the sheet (the end of the range I want to average) we will work on.
To start, I created a blank column next to the "power", column C.  Next to the last value we want to average is the box we will use for the Function.  It really does not matter where you put it, but this looks convenient.

While in that black box, click the Function button again.
We have a new one to try now, "Average", double click it and the following will appear:

Press the circled red box to bring up another range entry input box.  

Although we can just put the range in here directly, it's easy to just shift-click the first and last values to get our range.

Here is what you will see after clicking back into the sheet on the last value (215 watts) in cell B647:

Then scroll up the sheet to find the first value (226watts-B287), then shift-click into that value (make sure it is in the B column):

The range is put in properly.  If this is correct, now press the enter key to put it away and you are back to this:

You could have typed it in manually as you now see.  Press the OK and you have your result:

I went back to cycling analytics web site and double checked the average:
Looks good!

I repeated the process with the Vent rate with the results.  We now have an average ventilation vs power for a given interval:

There are many other interesting metrics to explore including, running averages, comparing line slopes and of course multiple sensor value combos in one graph.
For now I think this is enough to get started, especially if this is all new to you.  To the data scientists out there, my apologies for oversimplification, omissions but to the untrained folks out there (like me) this will get you up and running.  As old Dr McCoy said on Start Trek many times, "I'm a doctor not a ......"
Although sites like Garmin, cycling analytics, Hexoskin, Humon and Golden Cheetah will present data, they don't combine all sensors of interest, nor provide the flexibility that a plain old spreadsheet with appropriate graphing does.  However, for monitoring metrics over time, providing meaningful peak values, tracking training loads and such, these sites are essential.  I encourage athletes to use a data repository you are comfortable with, as well as learning to extract the individual sensor info for more in depth analysis.

Data Collection and Analysis - A How to Guide

Although no new scientific ground will be covered in this post, I believe the time has come to go over in detail how to extract, present and analyze the various data points collected in a work out session or testing program.  If only a simple quick look is needed, uploading to the various online sites or the Hexoskin web app will suffice.  But for anything more detailed and especially for mixing different sensor types, please continue to read below. 

Readers of this blog may have noted that there has been some better looking graphics over the past two years of posts.  In the beginning, I simply used the cyclinganalytics website or Garmin Connect embedded systems to present data.  After getting the Hexoskin, I used that website's graphic display to show data.  This resulted in a somewhat messy 2 or 3 graph presentation of each session or interval, without a clear side by side matching of the different sensor values.  This began to spiral into even more confusion with the use of up to 4 muscle O2 sensors along with Hexoskin ventilation, Hexoskin heart rate, Moov heart rate and Cycling Power.  Finally, I wanted to start tracking HHb instead of just simple muscle O2 saturation.  The solution was to independently extract each piece of data into a spreadsheet, calculate the HHb, then use a simple web based graphing app for presentation.  The post that follows will be a guide in how to do this.  The next post,
graphing and analysis, will continue this discussion.

I am going to assume that users will be recording the data either with a Garmin device (watch, cycling computer) or an android phone with ipBike and optionally have the Hexoskin for ventilation/heart rate recording.  I'll also show how to add in the muscle O2 data from the Humon site as well.

If you have a Fenix 5 or higher, it is possible to record data from 3 muscle O2 sensors.  You have the ability to have 2 connect IQ fields and set them for Moxy 1 and Moxy 2 data fields.  The third O2 field will be the native muscle O2 field that Garmin supports on the watch, but does not display on Garmin connect (so to many users the native O2 is worthless).  If you do have an android device with Ant+ (internal or via external USB), ipBike does support 1 muscle O2 field, which now gives us 4 total.  Try not to use the 2 Connect IQ fields for other sensors if possible since we need them for SmO2.  If possible, record BTLE or bluetooth smart with a Garmin native field or using an app on your phone.

In regards to heart rate, I strongly recommend either the Hexoskin or the Moov HR forehead unit.  I use both simultaneously to avoid data loss in the event of poor tracing quality.  You could pair either to the Garmin or ipBike, but the Hexoskin data will be in their download file.  However, any paired heart rate unit's data can be incorporated, just be aware of potential quality issues.

Make sure cycling power is paired to your Garmin unit, or recorded by some device that has download capability (android unit with ipBike).

Although starting the multiple recording units together is helpful, it's not essential.  We will need to match up the start time somehow, but I'll make some suggestions later.

Step one:
Download the .fit file from Garmin (or the .fit file from ipBike to be shown after).
If you go to the activity page, there is a pull down for downloading the file.  Choose the "original file" and save the .zip that is downloaded.  

You will then need to pull the file from the .zip and save that somewhere handy.
As an alternate (or complement), you can take the .csv or .fit file from ipBike or cycling analytics.  For time matching, the .fit file has a better time stamp.
Here is the screen from cycling analytics:

Step Two:
Install and prepare Golden Cheetah software.  This is a fantastic open source tool for tracking cycling activity, but should be able to handle run/ski as well (won't have cycling power).  If you have a Stryd, there is a way to have running power as well.
After installation (just make sure you have read the setup instructions), we need to import the Garmin .fit file into the app.  Find (under the Activity menu), "import from file" or control-i (red arrow step 1).  This will bring up a dialog box to find the .fit file you extracted.  Choose it, and it will be added and loaded.  
We then need to get our data out of here to a custom .csv file.  We don't want all the fields such as power balance, location , speed etc.  The first step is to go to the edit menu (red step 2).

The "Edit" window looks like this:

You can see the data fields on top and the time all the way on the left.  Each entry is 1 second.

To select a group of values is simple.  Just click on the first data point of interest (Time 0.0 for example), then shift click to the last field that will encompass the group (heart rate #42):
We will really want more data than that and to scroll to the end (or elsewhere) look to the right top and there is tiny scroll bar that can be moved with a mouse drag.
Let's take the entire ride worth of time, power, heart rate first and put it into a spreadsheet.  We will add ventilation, SmO2 later.
I have selected the data and will paste this into a blank Openoffice spreadsheet:
Paste into Row 2 since we will need to put names of the fields in the first Row.
This is the dialog box that you will see on the paste:

The columns look correct, so just hit ok.

You will then have this:

What I have done is simply put titles in the first row for time, power and heart rate.
Since we don't need columns B and D, delete them by right clicking on the top of the column (the B and D) then choose delete column.
This will be the result:

Now let's add some SmO2 data.
To do so, go back to the Golden Cheetah program and the .fit file, scroll to the right top of the edit screen:

This is the "native" muscle O2 field of the Fenix 5.  It is not the Connect IQ Moxy data field.  The nice thing about using this is that it will free up both Connect IQ fields to be used as O2 sensors as well for a total of 3.

The Moxy fields are on a different tab, the "Developer":

Here are the two Moxy fields on my Fenix 5:

Starting with the Standard tab, we will copy the entire list of SmO2 and Thb (I only selected a portion here, you should select the same range as in the power/time from above):
Then paste into the second row at the next blank column:

The dialog box is similar to the time/power:

With the result as shown (titles were put in):

 We are going to repeat the process with the other 2 O2 Moxy fields on the Developer tab:

Copy the entire bank of 4 columns and paste into the next blank column at row 2, then title the column fields:

You may want to save the spreadsheet now just in case.

Adding HHb:
Since many studies use deoxy hemogloblin instead of O2 saturation as the key metric, let's take a look at how to put that into our sheet.
The initial step is to add a column next to the calf O2 and Thb - to add a blank column just right click to the right side of the Header of where you want to put it and choose "insert column" from the menu:
Now, we need to create a "Function" to calculate this value.  Click into the first blank data box of the new column, then press the function button once:

The function wizard looks like this:

What you will need to do is scroll down to the "Product" function in the list on the left.
You will need to double click the Product function to choose it:

The next step is not difficult but if you are not familiar with spreadsheets it may be tricky.
The HHb is calculated by taking (100-saturated O2) which is the percent desaturation, then multiplying this by the total Hemoglobin.  Since the total Hb = saturated + desaturated, this equation is valid.

Here we go:

The Product function simply multiplies two numbers.  The first (Number 1) is the fraction of deoxy Hb (100-O2 sat/100).  The second number is the Thb for that row.  Make sure the row for the O2 sat and THb match.  As shown, the result is even calculated.  Hit ok when done.

Now we have 1 row's worth of HHb:

The next step is to populate the rest of the rows going down.  With the box selection above (black box with little square in the bottom corner), press control C to copy the formula.  Next step is to select the rest of the rows starting with row 3.  Click once in row 3 (column F in this case). 

Followed by:
Then scroll all the way down the sheet to the end and shift-click into the last box of Column F.

Once all the rows of the column are selected, press Control-V to paste the equation into the list.  Don't worry, the program automatically changes the calculations for each row correctly.
This is the result (the bottom of the list, so you don't see the titles):
Now we just have to title the top row to "calf HHb" and repeat the process with the other 2 O2 fields.

Here is the final result:

Hexoskin data
The next step is going to be skipped for most readers but for the few that have the Hexoskin and want to incorporate ventilation metrics let's see how.
You will need to download the .csv file for the particular workout matching the SmO2/power file.  Find the date in question, open the workout and hit the download button (down arrow circled in red below)
Here is where to do it:
Save the .CSV then open it:

Here is the raw .csv file:
Notice that the column titles don't match up.  The heart rate is under activity!  The minute ventilation is multiplied by 1000 which we will fix shortly.  The sleep position is actually the motion sensor, it will spike up at the start of a sprint for example (useful for matching exact time between files).  
The problem now is to decide where the file matches up to the original .fit ride or run file, so the data can be pasted in side by side with matching time.  

Step one is to see what time we started.  Go to the Raw view and the start time will be shown:
The hexoskin file starts at 7:55:43 and each row is 1 second of data.
Going back to the .fit file in Golden Cheetah the start time is obtained by moving to the "Details" tab:

The SmO2/power .fit file starts at 8:10:54 with a difference of 911 seconds from the Hexoskin.  Therefore we will go to row 912 of the Hexoskin sheet and copy the 3 columns to the end:
You will need to go to the very bottom and shift-click to get all the data (control C).

Then paste into the second row of the master file we were working on (box in black):

This is what you should have:

Let's fix the ventilation first.  We will need to add another column, so right click the top of column N and choose "add column". 
Then, Click on the first value of the new blank column (I called it "True Vent") and select the Function button again:
For this equation we want the "round" function.  Double click the Round item and just divide it by 1000, then hit ok.  You will only have that one row corrected, so we need that trick again to populate all the way down.  With that first value selected (N2), press Control C to copy.  Then single click to select the row below it, then scroll to the bottom to shift-click to select the entire range. Then press Control C to paste:

We are pretty well set but I do a fine fine tune match since I'm OCD.

To match the files exactly, find a spot in the session right before an intense effort.  Ideally, it would have been proceeded by a coast, then a fast start.  Here is an example:
The left red arrow is pointing to where I began pedaling hard, the right arrow is the activity column from the Hexoskin.  Notice the activity markedly increases about 4 rows (secs) before the watts begin.  To fix that, we simply need to shift the hexoskin data down 4 rows.

To do so, go back to the top, highlight the first row of just the Hexoskin data (click the first field then shift click to the far right).  Right click the blue highlighted area and choose "insert":
You will just want to hit OK to shift the cells down.  Repeat 3 more times to get the 4 second total discrepancy corrected.

Going back to the sprint interval, the beginning of power and hexoskin activity now match:
Adding Ipbike .csv data:
For those who are using the Android app ipBike, export the completed file as a .fit, find the download then open it in Golden Cheetah (same as doing it above).
On the Details tab, look to see that the time matches the Garmin device (I usually start them at the same time):
This day the times were way off and it turned out the ipBike file started much later, 8:57 instead of 8:10 (I think I hit the restart button by mistake on the warm up).  No problem - The difference is 2782 seconds.

The only data I need from this file is the SmO2 (costal location), since I record that sensor location on my Ant+ enabled android phone.  We are just going to copy and paste this into the master sheet.

Scroll to the far right and find SmO2:

Click on the first value then scroll all the way to the end and shift click on the last THb value:
Press CTRL-C to copy the data, go back to your master sheet and paste into the next free column at 2783 (remember the time difference):

This will be the result:

To double check, I pasted in the power data from ipBike (with the SmO2) and compared it to the one from Garmin.  The sprint spike was off by 4 seconds so I just moved the 3 columns from ipbike (O2, thb and Pwr) down:

Adding the HHb column to the Costal O2 data was also done.

Download from the Humon website:
In the above example, my Humon sensor is paired to my Fenix 5 as the native muscle O2 field so I generally do not need the data from the Hex itself.  But if you have a Fenix 3 for example that does not have native SmO2.  But if you have 3 sensors all is not lost.  Simply pair your other 2 sensors to the F3, start the Hex with your phone, then end the workout when done (on the phone).  The Hex app will upload the workout with it's data to their website.  You will now have the Garmin fit file with 2 sensors worth of data and the Humon web site will have a file for the Hex:

Above is the dialog box you will get for that particular file when downloading.  Export it as a .csv then open it:

As you can see there is a time stamp to compare and line up with the Garmin fit file as well as the smo2/hgb data fields.  We will then copy the 2 columns starting at the appropriate time differential:
Doing the same procedure as described above, click in the smo2 at the proper start position then scroll, shift-click at the very bottom:
Press CTRL C to copy, then paste into the master spreadsheet, do your HHb calculation, put title headers at the top and we are done.

Now that we now have a completed spreadsheet with time, power, heart rate, ventilation and SmO2 data from 4 sensors, the next step is graphing and analysis.

See also:
Analysis of Hexoskin binary RR interval and respiratory .wav data 

Smart trainer usage in physiologic testing and interval training  

Many thanks to my son Kipp for the introducing me to the basic methods of spreadsheets.