Function Repository Resource:

FITImport

Source Notebook

Import a FIT file to analyze data from cycling computers, smart watches and other devices

Contributed by: Richard Hennigan (Wolfram Research)

ResourceFunction["FITImport"][source]

imports data from a FIT file source as a Dataset.

ResourceFunction["FITImport"][source,elements]

imports the specified elements from a file.

Details and Options

Some possible values for source are:
"path"a string corresponding to a file path or URL
File[]a File object
URL[]a URL object
LocalObject[]a LocalObject
CloudObject[]a CloudObject
HTTPResponse[]an HTTPResponse object
Valid elements include:
"Dataset"gives a Dataset of records
"Data"gives a list of associations for records
"MessageCounts"gives an association of counts of each message type contained in the file
"MessageInformation"gives a Dataset containing meta information about messages
"RawData"gives raw data from the file as a matrix of integers
"prop"a TimeSeries for the specified record property
"type"a Dataset for the specified message type
{"prop1","prop2",}an association of TimeSeries objects for each "propi"
{"type1","type2",}a Dataset containing all messages of the specified types
Allimports all record properties as an association of TimeSeries objects
{"TimeSeries",elem}specifies that elem should be imported as a record property
Some additional cycling-specific visualization elements are:
"PowerZonePlot"gives a timeline plot of power zone levels over time
"AveragePowerPhasePlot"gives a visualization of left-right pedal power phase balance
"CriticalPowerCurvePlot"gives a plot of the estimated critical power curve
For a string element "elem" that matches both a message type and a record property, the message type is used.
To specify that "elem" is a record property, use {"TimeSeries","elem"}.
A few common message types are:
"Record"a timestamped message that's used for many of the default import elements
"DeviceInformation"a message containing information about a device that's generating data for a FIT file
"DeviceSettings"information about hardware settings for a device
"Event"an arbitrary event that's recorded
"FileID"information about the FIT file
"Session"information that typically aggregates record values as a summary
"UserProfile"user preferences that can affect how the FIT value is interpreted
The FIT protocol currently defines approximately 100 message types, though not all are supported by ResourceFunction["FITImport"]. Use ResourceFunction["FITImport"][file,"MessageInformation"] to see which message types in file can be imported.
ResourceFunction["FITImport"] is not currently compatible with the cloud or MacOSX-ARM64 systems.

Examples

Basic Examples (7) 

Import a fit file as a Dataset:

In[1]:=
ResourceFunction["FITImport"]["ExampleData/BikeRide.fit"]
Out[1]=

Get a summary of the ride:

In[2]:=
ResourceFunction["FITImport"]["ExampleData/BikeRide.fit", "Session"]
Out[2]=

Get a TimeSeries of GPS coordinates:

In[3]:=
ResourceFunction[
 "FITImport"]["ExampleData/BikeRide.fit", "GeoPosition"]
Out[3]=

View a map of the route:

In[4]:=
GeoGraphics[{Red, Thick, Line[Values[%]]}]
Out[4]=

Plot elevation over time:

In[5]:=
DateListPlot[
 ResourceFunction["FITImport"]["ExampleData/BikeHillClimb.fit", "Altitude"]]
Out[5]=

Visualize a workout:

In[6]:=
ResourceFunction[
 "FITImport"]["ExampleData/IndoorIntervals.fit", "PowerZonePlot"]
Out[6]=

Visualize the power phase of pedal strokes in a bike ride:

In[7]:=
ResourceFunction[
 "FITImport"]["ExampleData/BikeLaps.fit", "AveragePowerPhasePlot"]
Out[7]=

Get the critical power curve for a bike ride:

In[8]:=
ResourceFunction[
 "FITImport"]["ExampleData/BikeLaps.fit", "CriticalPowerCurvePlot"]
Out[8]=

Scope (3) 

Import all record values as TimeSeries objects:

In[9]:=
ResourceFunction["FITImport"]["ExampleData/Debugging.fit", All]
Out[9]=

Get information about devices used to generate the file:

In[10]:=
ResourceFunction[
 "FITImport"]["ExampleData/BikeHillClimb.fit", "DeviceInformation"]
Out[10]=

Get a summary of an activity:

In[11]:=
ResourceFunction["FITImport"]["ExampleData/Walk.fit", "Session"]
Out[11]=

Options (7) 

FunctionalThresholdPower (3) 

Some values are not available without specifying an FTP value:

In[12]:=
ResourceFunction[
 "FITImport"]["ExampleData/ZwiftRide.fit", "PowerZone"]
Out[12]=

Specify an FTP when importing to get power zone data:

In[13]:=
ResourceFunction[
 "FITImport"]["ExampleData/ZwiftRide.fit", "PowerZone", "FunctionalThresholdPower" -> Quantity[250, "Watts"]]
Out[13]=
In[14]:=
Histogram[Values[%]]
Out[14]=

See how the distribution changes with different FTP values:

In[15]:=
Histogram[
   Values[ResourceFunction["FITImport"]["ExampleData/ZwiftRide.fit", "PowerZone", "FunctionalThresholdPower" -> Quantity[#, "Watts"]]]] & /@ {150, 250, 350}
Out[15]=

Set the FTP persistently:

In[16]:=
PersistentSymbol["FITImport/FunctionalThresholdPower"] = Quantity[250, "Watts"]
Out[16]=

Specifying the FunctionalThresholdPower option is no longer necessary:

In[17]:=
ResourceFunction[
 "FITImport"]["ExampleData/ZwiftRide.fit", "PowerZone"]
Out[17]=

Remove the stored value:

In[18]:=
PersistentSymbol["FITImport/FunctionalThresholdPower"] =.

Some fit files have a stored FTP value:

In[19]:=
ResourceFunction[
 "FITImport"]["ExampleData/IndoorIntervals.fit", "PowerZonePlot"]
Out[19]=

Override the value stored in the file:

In[20]:=
ResourceFunction[
 "FITImport"]["ExampleData/IndoorIntervals.fit", "PowerZonePlot", "FunctionalThresholdPower" -> Quantity[400, "Watts"]]
Out[20]=

Other files may not store this information:

In[21]:=
ResourceFunction[
 "FITImport"]["ExampleData/ZwiftRide.fit", "PowerZonePlot"]
Out[21]=

Set the FTP manually:

In[22]:=
ResourceFunction[
 "FITImport"]["ExampleData/ZwiftRide.fit", "PowerZonePlot", "FunctionalThresholdPower" -> Quantity[250, "Watts"]]
Out[22]=

MaxHeartRate (2) 

Some values are not available without specifying a maximum heart rate:

In[23]:=
ResourceFunction[
 "FITImport"]["ExampleData/ZwiftRide.fit", "HeartRateZone"]
Out[23]=

Specify a max heart rate when importing to get HR zone data:

In[24]:=
ResourceFunction[
 "FITImport"]["ExampleData/ZwiftRide.fit", "HeartRateZone", "MaxHeartRate" -> 190]
Out[24]=
In[25]:=
Histogram[Values[%]]
Out[25]=

Set the max heart rate persistently:

In[26]:=
PersistentSymbol["FITImport/MaxHeartRate"] = Quantity[190, "BPM"]
Out[26]=

Specifying the MaxHeartRate option is no longer necessary:

In[27]:=
ResourceFunction[
 "FITImport"]["ExampleData/ZwiftRide.fit", "HeartRateZone"]
Out[27]=

Remove the stored value:

In[28]:=
PersistentSymbol["FITImport/MaxHeartRate"] =.

UnitSystem (2) 

By default, units are determined by the current value of $UnitSystem:

In[29]:=
$UnitSystem
Out[29]=
In[30]:=
ResourceFunction["FITImport"]["ExampleData/BikeHillClimb.fit", "Altitude"]["LastValue"]
Out[30]=

Override the default unit system:

In[31]:=
ResourceFunction["FITImport"]["ExampleData/BikeHillClimb.fit", "Altitude", UnitSystem -> "Metric"]["LastValue"]
Out[31]=

Properties and Relations (7) 

Some units depend on the activity type:

In[32]:=
Mean[ResourceFunction["FITImport"]["ExampleData/Walk.fit", "Cadence"]]
Out[32]=
In[33]:=
Mean[ResourceFunction["FITImport"]["ExampleData/BikeRide.fit", "Cadence"]]
Out[33]=

Get information about the fit protocol messages contained in a file:

In[34]:=
ResourceFunction[
 "FITImport"]["ExampleData/BikeRide.fit", "MessageInformation"]
Out[34]=

Check for message types that are not currently importable:

In[35]:=
Select[%, ! #["Supported"] &]
Out[35]=

Get the number of each message type in a fit file:

In[36]:=
ResourceFunction[
 "FITImport"]["ExampleData/BikeRide.fit", "MessageCounts"]
Out[36]=

Import specific message types:

In[37]:=
ResourceFunction[
 "FITImport"]["ExampleData/BikeRide.fit", {"FileID", "Lap"}]
Out[37]=

Get the raw message data as a matrix of integers:

In[38]:=
ResourceFunction["FITImport"]["ExampleData/BikeRide.fit", "RawData"]
Out[38]=

The first number in each row corresponds to the message type:

In[39]:=
%[[All, 1]] // Counts
Out[39]=
In[40]:=
ResourceFunction[
 "FITImport"]["ExampleData/BikeRide.fit", "MessageCounts"]
Out[40]=

It's much faster to import a time series directly rather than construct it manually:

In[41]:=
AbsoluteTiming[
 ResourceFunction["FITImport"]["ExampleData/BikeRide.fit", "Power"]]
Out[41]=
In[42]:=
AbsoluteTiming[
 TimeSeries[
  Values[Normal[
    ResourceFunction["FITImport"]["ExampleData/BikeRide.fit"][
     All, {"Timestamp", "Power"}]]]]]
Out[42]=

The "MessageCounts" property can be used to quickly find what kinds of messages are contained in a fit file:

In[43]:=
AbsoluteTiming[
 ResourceFunction["FITImport"]["ExampleData/BikeRide.fit", "MessageCounts"]]
Out[43]=
In[44]:=
AbsoluteTiming[
 ResourceFunction["FITImport"]["ExampleData/BikeRide.fit", "Sport"]]
Out[44]=

Compare to counting by importing every message first:

In[45]:=
AbsoluteTiming[
 CountsBy[
  ResourceFunction["FITImport"]["ExampleData/BikeRide.fit", "Messages"], #MessageType &]]
Out[45]=

Possible Issues (2) 

The type of data available can vary significantly between different fit files:

In[46]:=
RandomChoice[ResourceFunction["FITImport"]["ExampleData/Walk.fit"]]
Out[46]=
In[47]:=
RandomChoice[
 ResourceFunction["FITImport"]["ExampleData/BikeRide.fit"]]
Out[47]=
In[48]:=
RandomChoice[
 ResourceFunction["FITImport"]["ExampleData/Debugging.fit"]]
Out[48]=

Some elements are only available for certain activity types:

In[49]:=
ResourceFunction[
 "FITImport"]["ExampleData/Walk.fit", "AveragePowerPhasePlot"]
Out[49]=

Some devices may not capture the necessary data for some elements:

In[50]:=
ResourceFunction[
 "FITImport"]["ExampleData/ZwiftRide.fit", "AveragePowerPhasePlot"]
Out[50]=
In[51]:=
ResourceFunction[
 "FITImport"]["ExampleData/ZwiftRide.fit", "DeviceInformation"]
Out[51]=

A power meter is typically required to capture power phase data:

In[52]:=
Select[ResourceFunction["FITImport"]["ExampleData/BikeLaps.fit", "DeviceInformation"], #DeviceType === "BikePower" &]
Out[52]=
In[53]:=
ResourceFunction[
 "FITImport"]["ExampleData/BikeLaps.fit", "AveragePowerPhasePlot"]
Out[53]=

Neat Examples (3) 

Animate a bike ride:

In[54]:=
alt = Values[
    ResourceFunction["FITImport"]["ExampleData/BikeHillClimb.fit", "Altitude"]][[;; ;; 10]];
pos = Values[
    ResourceFunction["FITImport"]["ExampleData/BikeHillClimb.fit", "GeoPosition"]][[;; ;; 10]];
bounds = GeoBoundingBox[pos];
Animate[{
  GeoGraphics[{Thick, Red, Line[Take[pos, n]]}, GeoRange -> bounds],
  Show[ListLinePlot[alt], ListLinePlot[Take[alt, n], Filling -> Bottom]]
  }, {n, 1, Length[pos], 1}, DefaultDuration -> 30]
Out[52]=

Plot all the values found in a fit file:

In[55]:=
Short[ts = KeyDrop[ResourceFunction["FITImport"]["ExampleData/ZwiftRide.fit", All], "GeoPosition"]]
Out[55]=
In[56]:=
Multicolumn[
 KeyValueMap[
  DateListPlot[TimeSeriesResample[#2, 30], PlotLabel -> #1] &, ts], 3]
Out[56]=

Animate a Zwift ride:

In[57]:=
pos = Reverse /@ Values[ResourceFunction["FITImport"]["ExampleData/ZwiftRide.fit", "GeoPosition"]][[All, 1]];
watopia = ImageResize[
   First@Import[
     "https://zwiftinsider.com/wp-content/uploads/2022/09/Watopia-2.15.pdf"], 500];
Animate[
 ImageCompose[Show[watopia, ImageSize -> 500], Graphics[{Thickness[.01], Yellow, Line[Take[pos, n]], Green, Disk[pos[[n]], 0.0005]}, Sequence[
   ImageSize -> 250, PlotRange -> {{166.89, 166.95}, {-11.689, -11.664}}]], Scaled[{0.2966, 0.3302}]],
 {n, 1, Length[pos], 1},
 DefaultDuration -> 30
 ]
Out[51]=

Version History

  • 1.0.0 – 28 November 2022

Related Resources

License Information