grimbough / FITfileR

R package for reading data from FIT files using only native R code, rather than relying on external libraries.
https://msmith.de/FITfileR
53 stars 13 forks source link

Reading file from Suunto #5

Closed prochlorothrix closed 4 years ago

prochlorothrix commented 4 years ago

I tried to open a .fit file downloaded from the Suunto app (iOS) and got the following error:

fit <- readFitFile("SuuntoPaddling.fit") Error: Column 0 can't be converted from integer to list

traceback() 5: stop(list(message = "Column 0 can't be converted from integer to list", call = NULL, cppstack = NULL)) 4: bindrows(x, .id) 3: bind_rows(scaffold[idx]) 2: .renameMessages(tmp[[1]], tmp[[2]], merge = mergeMessages) 1: readFitFile("SuuntoPaddling.fit")

I don't understand why this happens. The data types seem fine to me

data_type_lookup $00 [1] "integer" "FALSE" "1" "1"
$01 [1] "integer" "TRUE" "1" "1"
$02 [1] "integer" "FALSE" "1" "1"
$83 [1] "integer" "TRUE" "2" "1"
$84 [1] "integer" "FALSE" "2" "1"
$85 [1] "integer" "TRUE" "4" "1"
$86 [1] "raw" "TRUE" "1" "4"
$07 [1] "character" "TRUE" "1" "1"
$88 [1] "numeric" "TRUE" "4" "1"
$89 [1] "numeric" "TRUE" "8" "1"
$0a [1] "integer" "FALSE" "1" "1"
$8b [1] "integer" "FALSE" "2" "1"
$8c [1] "raw" "TRUE" "1" "4"

grimbough commented 4 years ago

I'm afraid I can't really help without access to the fit file you're trying to read. The file format is pretty complex with many options, and I suspect the Suunto app is producing a file with some feature that fitFileR doesn't recognise and parses incorrectly.

prochlorothrix commented 4 years ago

I'm willing to share the file but I don't want it to be publicly available (it's an activity of one of the athletes I'm training). Is it OK if I send it to your private email address?

grimbough commented 4 years ago

Yes, sure. You can find my email in the DESCRIPTION file.

You might also want to try again with the latest version of the package. I made some updates to address #4 and there's a chance that'll fix you issue too. If not feel free to send me the file.

prochlorothrix commented 4 years ago

I tried the new version. The error still occurs:

fit <- readFitFile("SuuntoExample.fit") Error: Column 0 can't be converted from integer to list

traceback() 5: stop(list(message = "Column 0 can't be converted from integer to list", call = NULL, cppstack = NULL)) 4: bindrows(x, .id) 3: bind_rows(scaffold[idx]) 2: .renameMessages(tmp[[1]], tmp[[2]], merge = mergeMessages) 1: readFitFile("SuuntoExample.fit")

grimbough commented 4 years ago

This file contains records of the type hrv, which I haven't seen before. Sometimes these are a single value, other times groups of 2, 3 or 4 values per record. This messes up my strategy of merging all records of the same type in fitFileR.

You should be able to read the file by setting the argument mergeMessages = FALSE. You'll get back a list where each entry is a data.frame consisting of all records that are contain identical fields, and the names of the entries indicate the type of record.

The list should contain almost all the data from the file, but there might be quite a bit of processing required depending on what features are of interest to you.

library(fitFileR)
suunto <- readFitFile("~/Downloads/SuuntoExample.fit", mergeMessages = FALSE)
names(suunto)
#>  [1] "file_id-1"           "developer_data_id-1" "field_description-1"
#>  [4] "field_description-2" "field_description-3" "hrv-1"              
#>  [7] "record-1"            "record-2"            "hrv-2"              
#> [10] "hrv-3"               "record-3"            "record-4"           
#> [13] "hrv-4"               "record-5"            "record-6"           
#> [16] "event-1"             "record-7"            "lap-1"              
#> [19] "lap-2"               "lap-3"               "lap-4"              
#> [22] "lap-5"               "event-2"             "session-1"          
#> [25] "activity-1"
prochlorothrix commented 4 years ago

That's a step ahead anyway. The most relevant information seem to be in 'record-6:

head(fit$record-6)

A tibble: 6 x 8

timestamp position_lat position_long distance heart_rate altitude speed

1 2020-07-15 10:21:04 679087931 176876698 19 97 660. 1.58 2 2020-07-15 10:21:06 679087931 176877233 22 102 660. 1.38 3 2020-07-15 10:21:08 679087931 176877806 25 114 660. 1.42 4 2020-07-15 10:21:10 679088007 176878531 29 107 660 1.58 5 2020-07-15 10:21:12 679088007 176879123 32 97 660 1.56 6 2020-07-15 10:21:14 679088160 176879657 35 90 660. 1.56 # … with 1 more variable: vertical_speed

While most fields do make sense, latitude and longitude do not (at least to me). Any hint about how they are represented? The altitude is wrong as well (exactly 500m higher than it should, I guess not by chance)

grimbough commented 4 years ago

The coordinates are stored as signed 32-bit integers in the FIT format, and we need to map them to signed degrees. Something like this should do the transformation for you:

> fit$`record-2` %>% 
    dplyr::mutate(position_lat = position_lat * (180 / (2^31)),
                  position_long = position_long * (180 / 2^31))
# A tibble: 1,545 x 8
   timestamp           position_lat position_long distance heart_rate altitude
   <dttm>                     <dbl>         <dbl>    <dbl>      <int>    <dbl>
 1 2020-07-15 10:21:04         56.9          14.8       19         97     660.
 2 2020-07-15 10:21:06         56.9          14.8       22        102     660.
 3 2020-07-15 10:21:08         56.9          14.8       25        114     660.
 4 2020-07-15 10:21:10         56.9          14.8       29        107     660 
 5 2020-07-15 10:21:12         56.9          14.8       32         97     660 
 6 2020-07-15 10:21:14         56.9          14.8       35         90     660.
# … with 1,535 more rows, and 2 more variables: speed <dbl>,
#   vertical_speed <dbl>

Regarding altitude, you're absolutely correct. Looking at the FIT specification, altitude is stored with a 500m offset which I don't currently take into account when reading the data out. You can safely subtract 500 to get the correct value, but I'll probably add this in an update soon.

I've also now realised how annoying that - in the list names is!

prochlorothrix commented 4 years ago

That works fine. Thank you.