rowingdata 3.6.26__tar.gz → 3.7.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (197) hide show
  1. {rowingdata-3.6.26/rowingdata.egg-info → rowingdata-3.7.1}/PKG-INFO +3 -2
  2. rowingdata-3.7.1/rowingdata/fitwrite.py +344 -0
  3. {rowingdata-3.6.26 → rowingdata-3.7.1}/rowingdata/rowingdata.py +105 -8
  4. {rowingdata-3.6.26 → rowingdata-3.7.1}/rowingdata/tcxtools.py +21 -2
  5. {rowingdata-3.6.26 → rowingdata-3.7.1/rowingdata.egg-info}/PKG-INFO +3 -2
  6. {rowingdata-3.6.26 → rowingdata-3.7.1}/rowingdata.egg-info/SOURCES.txt +2 -0
  7. {rowingdata-3.6.26 → rowingdata-3.7.1}/rowingdata.egg-info/requires.txt +1 -0
  8. {rowingdata-3.6.26 → rowingdata-3.7.1}/setup.py +10 -2
  9. rowingdata-3.7.1/testdata/cottwich.csv +796 -0
  10. {rowingdata-3.6.26 → rowingdata-3.7.1}/tests/test_rowingdata.py +70 -0
  11. {rowingdata-3.6.26 → rowingdata-3.7.1}/LICENSE +0 -0
  12. {rowingdata-3.6.26 → rowingdata-3.7.1}/MANIFEST.in +0 -0
  13. {rowingdata-3.6.26 → rowingdata-3.7.1}/README.rst +0 -0
  14. {rowingdata-3.6.26 → rowingdata-3.7.1}/bin/crewnerddata.csv +0 -0
  15. {rowingdata-3.6.26 → rowingdata-3.7.1}/bin/crewnerddata.tcx +0 -0
  16. {rowingdata-3.6.26 → rowingdata-3.7.1}/bin/testdata.csv +0 -0
  17. {rowingdata-3.6.26 → rowingdata-3.7.1}/docs/_build/html/.buildinfo +0 -0
  18. {rowingdata-3.6.26 → rowingdata-3.7.1}/docs/_build/html/_images/2x20min.png +0 -0
  19. {rowingdata-3.6.26 → rowingdata-3.7.1}/docs/_build/html/_images/editrower.JPG +0 -0
  20. {rowingdata-3.6.26 → rowingdata-3.7.1}/docs/_build/html/_images/greghoc.png +0 -0
  21. {rowingdata-3.6.26 → rowingdata-3.7.1}/docs/_build/html/_images/image001.png +0 -0
  22. {rowingdata-3.6.26 → rowingdata-3.7.1}/docs/_build/html/_images/otwlogbook.JPG +0 -0
  23. {rowingdata-3.6.26 → rowingdata-3.7.1}/docs/_build/html/_images/otwscreenshot.JPG +0 -0
  24. {rowingdata-3.6.26 → rowingdata-3.7.1}/docs/_build/html/_images/screenshot.JPG +0 -0
  25. {rowingdata-3.6.26 → rowingdata-3.7.1}/docs/_build/html/_images/screenshotlogbook.JPG +0 -0
  26. {rowingdata-3.6.26 → rowingdata-3.7.1}/docs/_build/html/_images/woensdag.png +0 -0
  27. {rowingdata-3.6.26 → rowingdata-3.7.1}/docs/_build/html/_sources/index.txt +0 -0
  28. {rowingdata-3.6.26 → rowingdata-3.7.1}/docs/_build/html/_sources/modules.txt +0 -0
  29. {rowingdata-3.6.26 → rowingdata-3.7.1}/docs/_build/html/_sources/rowingdata.txt +0 -0
  30. {rowingdata-3.6.26 → rowingdata-3.7.1}/docs/_build/html/_static/ajax-loader.gif +0 -0
  31. {rowingdata-3.6.26 → rowingdata-3.7.1}/docs/_build/html/_static/alabaster.css +0 -0
  32. {rowingdata-3.6.26 → rowingdata-3.7.1}/docs/_build/html/_static/basic.css +0 -0
  33. {rowingdata-3.6.26 → rowingdata-3.7.1}/docs/_build/html/_static/comment-bright.png +0 -0
  34. {rowingdata-3.6.26 → rowingdata-3.7.1}/docs/_build/html/_static/comment-close.png +0 -0
  35. {rowingdata-3.6.26 → rowingdata-3.7.1}/docs/_build/html/_static/comment.png +0 -0
  36. {rowingdata-3.6.26 → rowingdata-3.7.1}/docs/_build/html/_static/custom.css +0 -0
  37. {rowingdata-3.6.26 → rowingdata-3.7.1}/docs/_build/html/_static/doctools.js +0 -0
  38. {rowingdata-3.6.26 → rowingdata-3.7.1}/docs/_build/html/_static/down-pressed.png +0 -0
  39. {rowingdata-3.6.26 → rowingdata-3.7.1}/docs/_build/html/_static/down.png +0 -0
  40. {rowingdata-3.6.26 → rowingdata-3.7.1}/docs/_build/html/_static/file.png +0 -0
  41. {rowingdata-3.6.26 → rowingdata-3.7.1}/docs/_build/html/_static/jquery-1.11.1.js +0 -0
  42. {rowingdata-3.6.26 → rowingdata-3.7.1}/docs/_build/html/_static/jquery.js +0 -0
  43. {rowingdata-3.6.26 → rowingdata-3.7.1}/docs/_build/html/_static/minus.png +0 -0
  44. {rowingdata-3.6.26 → rowingdata-3.7.1}/docs/_build/html/_static/plus.png +0 -0
  45. {rowingdata-3.6.26 → rowingdata-3.7.1}/docs/_build/html/_static/pygments.css +0 -0
  46. {rowingdata-3.6.26 → rowingdata-3.7.1}/docs/_build/html/_static/searchtools.js +0 -0
  47. {rowingdata-3.6.26 → rowingdata-3.7.1}/docs/_build/html/_static/underscore-1.3.1.js +0 -0
  48. {rowingdata-3.6.26 → rowingdata-3.7.1}/docs/_build/html/_static/underscore.js +0 -0
  49. {rowingdata-3.6.26 → rowingdata-3.7.1}/docs/_build/html/_static/up-pressed.png +0 -0
  50. {rowingdata-3.6.26 → rowingdata-3.7.1}/docs/_build/html/_static/up.png +0 -0
  51. {rowingdata-3.6.26 → rowingdata-3.7.1}/docs/_build/html/_static/websupport.js +0 -0
  52. {rowingdata-3.6.26 → rowingdata-3.7.1}/docs/_build/html/genindex.html +0 -0
  53. {rowingdata-3.6.26 → rowingdata-3.7.1}/docs/_build/html/index.html +0 -0
  54. {rowingdata-3.6.26 → rowingdata-3.7.1}/docs/_build/html/modules.html +0 -0
  55. {rowingdata-3.6.26 → rowingdata-3.7.1}/docs/_build/html/objects.inv +0 -0
  56. {rowingdata-3.6.26 → rowingdata-3.7.1}/docs/_build/html/py-modindex.html +0 -0
  57. {rowingdata-3.6.26 → rowingdata-3.7.1}/docs/_build/html/rowingdata.html +0 -0
  58. {rowingdata-3.6.26 → rowingdata-3.7.1}/docs/_build/html/search.html +0 -0
  59. {rowingdata-3.6.26 → rowingdata-3.7.1}/docs/_build/html/searchindex.js +0 -0
  60. {rowingdata-3.6.26 → rowingdata-3.7.1}/rowingdata/__init__.py +0 -0
  61. {rowingdata-3.6.26 → rowingdata-3.7.1}/rowingdata/__main__.py +0 -0
  62. {rowingdata-3.6.26 → rowingdata-3.7.1}/rowingdata/boatedit.py +0 -0
  63. {rowingdata-3.6.26 → rowingdata-3.7.1}/rowingdata/checkdatafiles.py +0 -0
  64. {rowingdata-3.6.26 → rowingdata-3.7.1}/rowingdata/copystats.py +0 -0
  65. {rowingdata-3.6.26 → rowingdata-3.7.1}/rowingdata/crewnerdplot.py +0 -0
  66. {rowingdata-3.6.26 → rowingdata-3.7.1}/rowingdata/crewnerdplottime.py +0 -0
  67. {rowingdata-3.6.26 → rowingdata-3.7.1}/rowingdata/csvparsers.py +0 -0
  68. {rowingdata-3.6.26 → rowingdata-3.7.1}/rowingdata/ergdataplot.py +0 -0
  69. {rowingdata-3.6.26 → rowingdata-3.7.1}/rowingdata/ergdataplottime.py +0 -0
  70. {rowingdata-3.6.26 → rowingdata-3.7.1}/rowingdata/ergdatatotcx.py +0 -0
  71. {rowingdata-3.6.26 → rowingdata-3.7.1}/rowingdata/ergstickplot.py +0 -0
  72. {rowingdata-3.6.26 → rowingdata-3.7.1}/rowingdata/ergstickplottime.py +0 -0
  73. {rowingdata-3.6.26 → rowingdata-3.7.1}/rowingdata/ergsticktotcx.py +0 -0
  74. {rowingdata-3.6.26 → rowingdata-3.7.1}/rowingdata/gpxtools.py +0 -0
  75. {rowingdata-3.6.26 → rowingdata-3.7.1}/rowingdata/gpxwrite.py +0 -0
  76. {rowingdata-3.6.26 → rowingdata-3.7.1}/rowingdata/konkatenaadje.py +0 -0
  77. {rowingdata-3.6.26 → rowingdata-3.7.1}/rowingdata/laptesting.py +0 -0
  78. {rowingdata-3.6.26 → rowingdata-3.7.1}/rowingdata/obsolete.py +0 -0
  79. {rowingdata-3.6.26 → rowingdata-3.7.1}/rowingdata/otherparsers.py +0 -0
  80. {rowingdata-3.6.26 → rowingdata-3.7.1}/rowingdata/painsled_desktop_plot.py +0 -0
  81. {rowingdata-3.6.26 → rowingdata-3.7.1}/rowingdata/painsled_desktop_plottime.py +0 -0
  82. {rowingdata-3.6.26 → rowingdata-3.7.1}/rowingdata/painsled_desktop_toc2.py +0 -0
  83. {rowingdata-3.6.26 → rowingdata-3.7.1}/rowingdata/painsledplot.py +0 -0
  84. {rowingdata-3.6.26 → rowingdata-3.7.1}/rowingdata/painsledplottime.py +0 -0
  85. {rowingdata-3.6.26 → rowingdata-3.7.1}/rowingdata/painsledtoc2.py +0 -0
  86. {rowingdata-3.6.26 → rowingdata-3.7.1}/rowingdata/roweredit.py +0 -0
  87. {rowingdata-3.6.26 → rowingdata-3.7.1}/rowingdata/rowproplot.py +0 -0
  88. {rowingdata-3.6.26 → rowingdata-3.7.1}/rowingdata/rowproplottime.py +0 -0
  89. {rowingdata-3.6.26 → rowingdata-3.7.1}/rowingdata/speedcoachplot.py +0 -0
  90. {rowingdata-3.6.26 → rowingdata-3.7.1}/rowingdata/speedcoachplottime.py +0 -0
  91. {rowingdata-3.6.26 → rowingdata-3.7.1}/rowingdata/speedcoachtoc2.py +0 -0
  92. {rowingdata-3.6.26 → rowingdata-3.7.1}/rowingdata/tcxplot.py +0 -0
  93. {rowingdata-3.6.26 → rowingdata-3.7.1}/rowingdata/tcxplot_nogeo.py +0 -0
  94. {rowingdata-3.6.26 → rowingdata-3.7.1}/rowingdata/tcxplottime.py +0 -0
  95. {rowingdata-3.6.26 → rowingdata-3.7.1}/rowingdata/tcxplottime_nogeo.py +0 -0
  96. {rowingdata-3.6.26 → rowingdata-3.7.1}/rowingdata/tcxtoc2.py +0 -0
  97. {rowingdata-3.6.26 → rowingdata-3.7.1}/rowingdata/trainingparser.py +0 -0
  98. {rowingdata-3.6.26 → rowingdata-3.7.1}/rowingdata/utils.py +0 -0
  99. {rowingdata-3.6.26 → rowingdata-3.7.1}/rowingdata/windcorrected.py +0 -0
  100. {rowingdata-3.6.26 → rowingdata-3.7.1}/rowingdata/writetcx.py +0 -0
  101. {rowingdata-3.6.26 → rowingdata-3.7.1}/rowingdata.egg-info/dependency_links.txt +0 -0
  102. {rowingdata-3.6.26 → rowingdata-3.7.1}/rowingdata.egg-info/not-zip-safe +0 -0
  103. {rowingdata-3.6.26 → rowingdata-3.7.1}/rowingdata.egg-info/top_level.txt +0 -0
  104. {rowingdata-3.6.26 → rowingdata-3.7.1}/setup.cfg +0 -0
  105. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/2016-03-25-0758.CSV +0 -0
  106. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/2016-03-25-0758_data.CSV +0 -0
  107. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/2x20min_o.csv +0 -0
  108. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/2x2km.tcx +0 -0
  109. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/3km_cd_o.csv +0 -0
  110. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/3x250m.tcx +0 -0
  111. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/EUBoatCoach.csv +0 -0
  112. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/EmpowerSpeedCoachForce.csv +0 -0
  113. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/NKEmporfromgreg.csv +0 -0
  114. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/NKLiNKv130.csv +0 -0
  115. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/NoHR.tcx +0 -0
  116. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/PainsledForce.csv +0 -0
  117. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/RP_interval.csv +0 -0
  118. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/RP_interval.csv_o.CSV +0 -0
  119. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/RP_testdata.csv +0 -0
  120. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/SpdCoach2_imp_inconsistent.csv +0 -0
  121. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/SpdCoachAmbiguous.csv +0 -0
  122. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/SpeedCoach GPS Workout.csv +0 -0
  123. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/SpeedCoach2Link_interval.csv +0 -0
  124. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/SpeedCoach2Linkv1.27.csv +0 -0
  125. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/SpeedCoach2v2.12.csv +0 -0
  126. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/Speedcoach2example.csv +0 -0
  127. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/aritmo.csv +0 -0
  128. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/bc1.csv +0 -0
  129. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/bc2.csv +0 -0
  130. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/bc3.csv +0 -0
  131. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/boatcoach.csv +0 -0
  132. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/boatcoach_fixed_distance.csv +0 -0
  133. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/boatcoach_otw.csv +0 -0
  134. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/correctedpainsled.csv +0 -0
  135. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/coxmate.csv +0 -0
  136. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/crewnerd_interval.csv +0 -0
  137. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/crewnerddata.CSV +0 -0
  138. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/crewnerddata.tcx +0 -0
  139. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/crewnerddata_o.CSV +0 -0
  140. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/cumvalues.csv +0 -0
  141. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/ergdata_example.csv +0 -0
  142. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/ergdata_example.csv_o.CSV +0 -0
  143. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/ergstick.csv +0 -0
  144. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/ergstick.csv_o.CSV +0 -0
  145. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/ergstick2.csv +0 -0
  146. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/ergstick_o.csv +0 -0
  147. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/example.csv +0 -0
  148. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/example_data.csv +0 -0
  149. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/faketcx.tcx +0 -0
  150. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/float.csv +0 -0
  151. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/herodata.csv +0 -0
  152. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/humon.csv +0 -0
  153. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/impeller_empower.csv +0 -0
  154. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/invalidchar.tcx +0 -0
  155. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/kinomap.csv +0 -0
  156. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/mystery.csv +0 -0
  157. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/nk_logbook.csv +0 -0
  158. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/painsled.tcx +0 -0
  159. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/painsled_desktop_example.csv +0 -0
  160. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/painsled_out_data.csv +0 -0
  161. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/painsled_to_csv_20160221-105218_erg-400150318_760465m.csv +0 -0
  162. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/quiske_per_stroke_left.csv +0 -0
  163. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/quiske_per_stroke_new.csv +0 -0
  164. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/quiske_per_stroke_right.csv +0 -0
  165. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/quiske_per_stroke_seat.csv +0 -0
  166. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/recover.tcx +0 -0
  167. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/ritmointervals.csv +0 -0
  168. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/rowingdata_eth.csv +0 -0
  169. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/rowinginmotionexample.TCX +0 -0
  170. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/rowinginmotionexample.TCX_o.CSV +0 -0
  171. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/rowperfect.csv +0 -0
  172. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/rowpro5.csv +0 -0
  173. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/rowpro_carrick.csv +0 -0
  174. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/rp3_curve.csv +0 -0
  175. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/rp3intervals.csv +0 -0
  176. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/rp3intervals2.csv +0 -0
  177. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/rp_out.csv +0 -0
  178. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/smartrow.csv +0 -0
  179. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/smartrow_intervals.csv +0 -0
  180. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/spdcoach2noheader.csv +0 -0
  181. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/speedcoach2test2.csv +0 -0
  182. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/speedcoach3test3.csv +0 -0
  183. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/speedcoachexample.csv +0 -0
  184. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/speedcoachexample.csv_o.CSV +0 -0
  185. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/strava_export.tcx +0 -0
  186. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/summarytest.csv +0 -0
  187. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/testdata.csv +0 -0
  188. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/testdata_part1.csv +0 -0
  189. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/testdata_part2.csv +0 -0
  190. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/testdatasummary.csv +0 -0
  191. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/testlapidx.csv +0 -0
  192. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/testtcs_210614.tcx +0 -0
  193. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/testtcx.tcx +0 -0
  194. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/testtcx_210614.tcx +0 -0
  195. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata/tim.csv +0 -0
  196. {rowingdata-3.6.26 → rowingdata-3.7.1}/testdata//345/210/222/350/210/271.csv" +0 -0
  197. {rowingdata-3.6.26 → rowingdata-3.7.1}/tests/testparser.py +0 -0
@@ -1,8 +1,8 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: rowingdata
3
- Version: 3.6.26
3
+ Version: 3.7.1
4
4
  Summary: The rowingdata library to create colorful plots from CrewNerd, Painsled and other rowing data tools
5
- Home-page:
5
+ Home-page: https://github.com/sanderroosendaal/rowingdata
6
6
  Author: Sander Roosendaal
7
7
  Author-email: roosendaalsander@gmail.com
8
8
  License: MIT
@@ -15,6 +15,7 @@ Requires-Dist: scipy
15
15
  Requires-Dist: matplotlib
16
16
  Requires-Dist: pandas
17
17
  Requires-Dist: fitparse
18
+ Requires-Dist: fit-tool>=0.9.14
18
19
  Requires-Dist: arrow>=1.0.2
19
20
  Requires-Dist: python-dateutil
20
21
  Requires-Dist: docopt
@@ -0,0 +1,344 @@
1
+ """
2
+ FIT file export for rowingdata.
3
+ Exports rowingdata DataFrames to Garmin FIT format for compatibility with
4
+ Intervals.icu and other platforms.
5
+ """
6
+ from __future__ import absolute_import
7
+ from __future__ import print_function
8
+
9
+ import datetime
10
+ import numpy as np
11
+ from dateutil import parser as ps
12
+ import arrow
13
+
14
+ try:
15
+ from fit_tool.base_type import BaseType
16
+ from fit_tool.developer_field import DeveloperField
17
+ from fit_tool.fit_file_builder import FitFileBuilder
18
+ from fit_tool.profile.messages.developer_data_id_message import DeveloperDataIdMessage
19
+ from fit_tool.profile.messages.field_description_message import FieldDescriptionMessage
20
+ from fit_tool.profile.messages.file_id_message import FileIdMessage
21
+ from fit_tool.profile.messages.activity_message import ActivityMessage
22
+ from fit_tool.profile.messages.session_message import SessionMessage
23
+ from fit_tool.profile.messages.lap_message import LapMessage
24
+ from fit_tool.profile.messages.record_message import RecordMessage
25
+ from fit_tool.profile.messages.event_message import EventMessage
26
+ from fit_tool.profile.profile_type import (
27
+ FileType, Manufacturer, Sport, SubSport,
28
+ Event, EventType, Activity,
29
+ )
30
+ FIT_TOOL_AVAILABLE = True
31
+ except ImportError:
32
+ FIT_TOOL_AVAILABLE = False
33
+
34
+ # Developer field definitions for rowing-specific columns (no native FIT equivalent).
35
+ # StrokeDistance is here: native cycle_length is uint8/scale100 (max 2.55m), wrong for rowing (7–12m typical).
36
+ # (field_id, df_column, name, base_type, size, scale, units)
37
+ ROWING_DEV_FIELDS = [
38
+ (0, ' DriveLength (meters)', 'DriveLength', BaseType.UINT16, 2, 100, 'm'),
39
+ (1, ' DriveTime (ms)', 'DriveTime', BaseType.UINT16, 2, 1, 'ms'),
40
+ (2, ' DragFactor', 'DragFactor', BaseType.UINT16, 2, 1, ''),
41
+ (3, ' StrokeRecoveryTime (ms)', 'StrokeRecoveryTime', BaseType.UINT16, 2, 1, 'ms'),
42
+ (4, ' AverageDriveForce (lbs)', 'AverageDriveForceLbs', BaseType.UINT16, 2, 10, 'lbs'),
43
+ (5, ' PeakDriveForce (lbs)', 'PeakDriveForceLbs', BaseType.UINT16, 2, 10, 'lbs'),
44
+ (6, ' AverageDriveForce (N)', 'AverageDriveForceN', BaseType.UINT16, 2, 10, 'N'),
45
+ (7, ' PeakDriveForce (N)', 'PeakDriveForceN', BaseType.UINT16, 2, 10, 'N'),
46
+ (8, ' AverageBoatSpeed (m/s)', 'AverageBoatSpeed', BaseType.UINT16, 2, 100, 'm/s'),
47
+ (9, ' WorkoutState', 'WorkoutState', BaseType.UINT8, 1, 1, ''),
48
+ (10, ' StrokeDistance (meters)', 'StrokeDistance', BaseType.UINT16, 2, 100, 'm'),
49
+ ]
50
+
51
+ # Sport string to FIT enum mapping
52
+ SPORT_MAP = {
53
+ 'rowing': Sport.ROWING,
54
+ 'water': Sport.ROWING,
55
+ 'indoor_rowing': Sport.ROWING,
56
+ 'indoor rowing': Sport.ROWING,
57
+ 'run': Sport.RUNNING,
58
+ 'running': Sport.RUNNING,
59
+ 'bike': Sport.CYCLING,
60
+ 'cycling': Sport.CYCLING,
61
+ 'swim': Sport.SWIMMING,
62
+ 'swimming': Sport.SWIMMING,
63
+ 'other': Sport.GENERIC,
64
+ }
65
+
66
+
67
+ def _valid_position(lat, lon):
68
+ """True if lat/lon are valid degrees for FIT (rough bounds)."""
69
+ return (not (np.isnan(lat) or np.isnan(lon)) and
70
+ -90 <= lat <= 90 and -180 <= lon <= 180 and (lat != 0 or lon != 0))
71
+
72
+
73
+ def _sport_to_fit(sport_str):
74
+ """Map sport string to FIT Sport enum."""
75
+ if sport_str is None:
76
+ return Sport.ROWING
77
+ key = str(sport_str).lower().strip()
78
+ return SPORT_MAP.get(key, Sport.ROWING)
79
+
80
+
81
+ def write_fit(file_name, df, row_date="2016-01-01", notes="Exported by Rowingdata",
82
+ sport="rowing", use_developer_fields=True):
83
+ """
84
+ Write rowingdata DataFrame to a FIT activity file.
85
+
86
+ Parameters
87
+ ----------
88
+ file_name : str
89
+ Output file path (e.g. 'activity.fit')
90
+ df : pandas.DataFrame
91
+ DataFrame with rowingdata columns (TimeStamp, Horizontal, Cadence, etc.)
92
+ row_date : str or datetime
93
+ Workout date (used when timestamps are relative)
94
+ notes : str
95
+ Activity notes
96
+ sport : str
97
+ Sport type: 'rowing', 'indoor_rowing', 'other', etc.
98
+ use_developer_fields : bool
99
+ If True, include rowing-specific columns as developer fields when present.
100
+ If False, export only standard FIT fields (timestamp, distance, cadence,
101
+ heart_rate, power, speed, position).
102
+ """
103
+ if not FIT_TOOL_AVAILABLE:
104
+ raise ImportError("fit-tool is required for FIT export. Install with: pip install fit-tool")
105
+
106
+ # Get or compute cum_dist
107
+ if 'cum_dist' not in df.columns and ' Horizontal (meters)' in df.columns:
108
+ from .csvparsers import make_cumvalues
109
+ res = make_cumvalues(df[' Horizontal (meters)'])
110
+ df = df.copy()
111
+ df['cum_dist'] = res[0]
112
+
113
+ nr_rows = len(df)
114
+ if nr_rows == 0:
115
+ raise ValueError("Cannot export empty DataFrame to FIT")
116
+
117
+ # Parse row_date and determine timestamps
118
+ dateobj = ps.parse(str(row_date))
119
+ timezero = arrow.get(datetime.datetime(2000, 1, 1)).timestamp()
120
+ seconds = df['TimeStamp (sec)'].values
121
+
122
+ if seconds[0] < timezero:
123
+ # Relative timestamps: add row_date
124
+ unixtimes = seconds + arrow.get(dateobj).timestamp()
125
+ else:
126
+ unixtimes = seconds
127
+
128
+ # FIT timestamps in milliseconds (fit-tool convention for timestamp fields)
129
+ start_time_ms = int(unixtimes[0] * 1000)
130
+ total_elapsed_ms = int((unixtimes[-1] - unixtimes[0]) * 1000) if nr_rows > 1 else 0
131
+ total_elapsed_s = (unixtimes[-1] - unixtimes[0]) if nr_rows > 1 else 0.0 # seconds, for Activity/Session/Lap
132
+
133
+ # Arrays for record messages
134
+ try:
135
+ distance_m = df['cum_dist'].values
136
+ except KeyError:
137
+ distance_m = df[' Horizontal (meters)'].values
138
+
139
+ try:
140
+ cadence = np.round(df[' Cadence (stokes/min)'].values).astype(int)
141
+ except KeyError:
142
+ cadence = np.zeros(nr_rows, dtype=int)
143
+
144
+ try:
145
+ heart_rate = df[' HRCur (bpm)'].values.astype(int)
146
+ heart_rate = np.clip(heart_rate, 0, 255) # uint8
147
+ except KeyError:
148
+ heart_rate = np.zeros(nr_rows, dtype=int)
149
+
150
+ try:
151
+ power = df[' Power (watts)'].values.astype(int)
152
+ power = np.clip(power, 0, 65535) # uint16
153
+ except KeyError:
154
+ power = np.zeros(nr_rows, dtype=int)
155
+
156
+ try:
157
+ pace = df[' Stroke500mPace (sec/500m)'].values
158
+ # enhanced_speed in m/s; avoid div by zero
159
+ pace_safe = np.where(pace > 0, pace, np.inf)
160
+ enhanced_speed = 500.0 / pace_safe
161
+ enhanced_speed = np.where(np.isfinite(enhanced_speed), enhanced_speed, 0)
162
+ except KeyError:
163
+ enhanced_speed = np.zeros(nr_rows)
164
+
165
+ try:
166
+ lat = df[' latitude'].values
167
+ lon = df[' longitude'].values
168
+ except KeyError:
169
+ lat = np.zeros(nr_rows)
170
+ lon = np.zeros(nr_rows)
171
+
172
+ # Developer fields: which columns exist and their arrays
173
+ use_dev = use_developer_fields and FIT_TOOL_AVAILABLE
174
+ dev_arrays = {}
175
+ dev_specs = []
176
+ DEV_DATA_IDX = 0
177
+ if use_dev:
178
+ for fd in ROWING_DEV_FIELDS:
179
+ field_id, col, name, base_type, size, scale, units = fd
180
+ if col in df.columns:
181
+ arr = df[col].values
182
+ arr = np.nan_to_num(arr, nan=0.0, posinf=0.0, neginf=0.0)
183
+ # Clip to avoid overflow: encoded = value * scale must fit in base_type
184
+ if base_type == BaseType.UINT8:
185
+ arr = np.clip(arr, 0, 255)
186
+ elif base_type == BaseType.UINT16:
187
+ max_display = 65535.0 / scale if scale else 65535.0
188
+ arr = np.clip(arr, 0, max_display)
189
+ dev_arrays[field_id] = arr
190
+ dev_specs.append((field_id, col, name, base_type, size, scale, units))
191
+
192
+ # Build FIT file
193
+ min_str = 50 if dev_specs else 8
194
+ builder = FitFileBuilder(auto_define=True, min_string_size=min_str)
195
+
196
+ # File ID
197
+ file_id = FileIdMessage()
198
+ file_id.type = FileType.ACTIVITY
199
+ file_id.manufacturer = Manufacturer.GARMIN.value
200
+ file_id.product = 0
201
+ file_id.time_created = start_time_ms
202
+ file_id.serial_number = 0x12345678
203
+ builder.add(file_id)
204
+
205
+ # Activity message
206
+ activity = ActivityMessage()
207
+ activity.timestamp = start_time_ms
208
+ activity.total_timer_time = total_elapsed_s # seconds; fit-tool applies scale 1000
209
+ activity.num_sessions = 1
210
+ activity.type = Activity.MANUAL
211
+ activity.event = Event.TIMER
212
+ activity.event_type = EventType.START
213
+ builder.add(activity)
214
+
215
+ # Timer start event
216
+ event_start = EventMessage()
217
+ event_start.event = Event.TIMER
218
+ event_start.event_type = EventType.START
219
+ event_start.timestamp = start_time_ms
220
+ builder.add(event_start)
221
+
222
+ # Session message
223
+ total_dist = float(distance_m[-1]) if nr_rows > 0 else 0
224
+ try:
225
+ total_calories = int(df[' Calories (kCal)'].max())
226
+ except (KeyError, ValueError):
227
+ total_calories = 0
228
+
229
+ avg_hr = int(np.mean(heart_rate[heart_rate > 0])) if np.any(heart_rate > 0) else 0
230
+ max_hr = int(np.max(heart_rate)) if nr_rows > 0 else 0
231
+ avg_cadence = int(np.mean(cadence[cadence > 0])) if np.any(cadence > 0) else 0
232
+ avg_power = int(np.mean(power[power > 0])) if np.any(power > 0) else 0
233
+
234
+ session = SessionMessage()
235
+ session.message_index = 0
236
+ session.timestamp = start_time_ms
237
+ session.start_time = start_time_ms
238
+ session.total_elapsed_time = total_elapsed_s # seconds; fit-tool applies scale 1000
239
+ session.total_timer_time = total_elapsed_s
240
+ session.total_distance = int(round(total_dist)) # fit-tool applies scale 100
241
+ session.total_calories = total_calories
242
+ session.sport = _sport_to_fit(sport)
243
+ session.sub_sport = SubSport.GENERIC
244
+ if avg_hr > 0:
245
+ session.avg_heart_rate = avg_hr
246
+ if max_hr > 0:
247
+ session.max_heart_rate = max_hr
248
+ if avg_cadence > 0:
249
+ session.avg_cadence = avg_cadence
250
+ if avg_power > 0:
251
+ session.avg_power = avg_power
252
+ builder.add(session)
253
+
254
+ # Lap message (one lap for the whole session for now)
255
+ lap = LapMessage()
256
+ lap.message_index = 0
257
+ lap.timestamp = int(unixtimes[-1] * 1000) if nr_rows > 0 else start_time_ms
258
+ lap.start_time = start_time_ms
259
+ lap.total_elapsed_time = total_elapsed_s # seconds; fit-tool applies scale 1000
260
+ lap.total_timer_time = total_elapsed_s
261
+ lap.total_distance = int(round(total_dist)) # fit-tool applies scale 100
262
+ lap.total_calories = total_calories
263
+ lap.sport = _sport_to_fit(sport)
264
+ lap.sub_sport = SubSport.GENERIC
265
+ if avg_hr > 0:
266
+ lap.avg_heart_rate = avg_hr
267
+ if max_hr > 0:
268
+ lap.max_heart_rate = max_hr
269
+ if avg_cadence > 0:
270
+ lap.avg_cadence = avg_cadence
271
+ if avg_power > 0:
272
+ lap.avg_power = avg_power
273
+ # fit-tool expects position in degrees (it applies semicircle conversion internally)
274
+ if _valid_position(lat[0], lon[0]) and _valid_position(lat[-1], lon[-1]):
275
+ lap.start_position_lat = float(lat[0])
276
+ lap.start_position_long = float(lon[0])
277
+ lap.end_position_lat = float(lat[-1])
278
+ lap.end_position_long = float(lon[-1])
279
+ builder.add(lap)
280
+
281
+ # Developer data (ID + field descriptions) when we have developer fields
282
+ DEV_DATA_IDX = 0
283
+ if dev_specs:
284
+ dev_id_msg = DeveloperDataIdMessage()
285
+ dev_id_msg.application_id = b'rowingdata'
286
+ dev_id_msg.developer_data_index = DEV_DATA_IDX
287
+ builder.add(dev_id_msg)
288
+ for field_id, col, name, base_type, size, scale, units in dev_specs:
289
+ fd_msg = FieldDescriptionMessage()
290
+ fd_msg.developer_data_index = DEV_DATA_IDX
291
+ fd_msg.field_definition_number = field_id
292
+ fd_msg.fit_base_type_id = base_type.value
293
+ fd_msg.field_name = name
294
+ fd_msg.scale = int(scale) if scale == int(scale) else scale
295
+ fd_msg.offset = 0
296
+ fd_msg.units = units
297
+ builder.add(fd_msg)
298
+
299
+ # Record messages
300
+ for i in range(nr_rows):
301
+ dev_fields = []
302
+ if dev_specs:
303
+ for field_id, col, name, base_type, size, scale, units in dev_specs:
304
+ arr = dev_arrays[field_id]
305
+ val = float(arr[i])
306
+ if val != 0 or field_id == 9: # WorkoutState can be 0
307
+ # Pass display value; fit-tool encodes as raw = (value + offset) * scale.
308
+ # FieldDescription has same scale so readers decode: display = raw/scale.
309
+ dev = DeveloperField(
310
+ developer_data_index=DEV_DATA_IDX,
311
+ field_id=field_id,
312
+ size=size,
313
+ name=name,
314
+ base_type=base_type,
315
+ scale=scale,
316
+ offset=0,
317
+ units=units
318
+ )
319
+ dev.set_value(0, val)
320
+ dev_fields.append(dev)
321
+ rec = RecordMessage(developer_fields=dev_fields) if dev_fields else RecordMessage()
322
+ rec.timestamp = int(unixtimes[i] * 1000)
323
+ rec.distance = float(distance_m[i]) # fit-tool applies scale 100
324
+ rec.heart_rate = heart_rate[i] if heart_rate[i] > 0 else None
325
+ rec.cadence = cadence[i] if cadence[i] > 0 else None
326
+ rec.power = power[i] if power[i] > 0 else None
327
+ # fit-tool expects display value (m/s); it applies scale 1000 internally
328
+ rec.enhanced_speed = float(enhanced_speed[i]) if enhanced_speed[i] > 0 else None
329
+
330
+ if not (np.isnan(lat[i]) or lat[i] == 0) and not (np.isnan(lon[i]) or lon[i] == 0):
331
+ rec.position_lat = float(lat[i])
332
+ rec.position_long = float(lon[i])
333
+
334
+ builder.add(rec)
335
+
336
+ # Timer stop event
337
+ event_stop = EventMessage()
338
+ event_stop.event = Event.TIMER
339
+ event_stop.event_type = EventType.STOP_ALL
340
+ event_stop.timestamp = int(unixtimes[-1] * 1000) if nr_rows > 0 else start_time_ms
341
+ builder.add(event_stop)
342
+
343
+ fit_file = builder.build()
344
+ fit_file.to_file(file_name)
@@ -5,7 +5,7 @@ from __future__ import print_function
5
5
  from six.moves import range
6
6
  from six.moves import input
7
7
 
8
- __version__ = "3.6.26"
8
+ __version__ = "3.7.1"
9
9
 
10
10
  from collections import Counter
11
11
 
@@ -83,10 +83,12 @@ try:
83
83
  from . import gpxwrite
84
84
  from . import trainingparser
85
85
  from . import writetcx
86
+ from . import fitwrite
86
87
  except (ValueError,ImportError): # pragma: no cover
87
88
  import rowingdata.gpxwrite
88
89
  import rowingdata.trainingparser
89
90
  import rowingdata.writetcx
91
+ import rowingdata.fitwrite
90
92
 
91
93
  import requests
92
94
 
@@ -166,7 +168,7 @@ def main(): # pragma: no cover
166
168
  def my_autopct(pct, cutoff=5): # pragma: no cover
167
169
  return ('%4.1f%%' % pct) if pct > cutoff else ''
168
170
 
169
- def nanstozero(nr):
171
+ def nanstozero(nr: float) -> float:
170
172
  if isnan(nr) or isinf(nr):
171
173
  return 0
172
174
  else:
@@ -187,7 +189,7 @@ def post_progress(secret,progressurl,progress): # pragma: no cover
187
189
 
188
190
 
189
191
  # toekomstmuziek - nog niet gebruikt
190
- def make_subplot(ax,r,df,param,mode=['distance','ote'],bars=None,barnames=None): # pragma: no cover
192
+ def make_subplot(ax,r,df,param,mode=['distance','ote'],bars=None,barnames=None,barlimits=None,barverbosenames=None): # pragma: no cover
191
193
  if 'distance' in mode:
192
194
  xcolumn = 'cum_dist'
193
195
  dist_max = 1000
@@ -207,10 +209,13 @@ def make_subplot(ax,r,df,param,mode=['distance','ote'],bars=None,barnames=None):
207
209
  if barlimits is None:
208
210
  barlimits = ['lim_ut2','lim_ut1','lim_at','lim_tr','lim_an','lim_max']
209
211
  if barverbosenames is None:
210
- barverbosenames = list(self.rwr.hrzones)
212
+ barverbosenames = list(r.rwr.hrzones)
211
213
 
212
214
  colors = ['gray','y','g','blue','violet','r']
213
215
 
216
+ dist_increments = df.loc[:, xcolumn].diff()
217
+ dist_increments[0] = dist_increments[1]
218
+
214
219
  for i in range(len(bars)):
215
220
  ax.bar(df.loc[:,xcolumn],df.loc[:,barnames[i]],
216
221
  width=dist_increments,
@@ -259,11 +264,12 @@ def make_subplot(ax,r,df,param,mode=['distance','ote'],bars=None,barnames=None):
259
264
  majorLocator = (1000)
260
265
 
261
266
 
267
+ majorTickFormatter = FuncFormatter(format_pace_tick)
262
268
  ax.yaxis.set_major_formatter(majorTickFormatter)
263
269
  if 'time' in mode:
264
270
  ax.set_xlabel('Time (sec)')
265
271
  else:
266
- ax.set_axlabel('Distance (m)')
272
+ ax.set_xlabel('Distance (m)')
267
273
 
268
274
  if end_dist < dist_max:
269
275
  ax.set_xticks(list(range(dist_tick, end_dist, dist_tick)))
@@ -1228,7 +1234,7 @@ class summarydata: # pragma: no cover
1228
1234
  totmin = totmin + 1
1229
1235
 
1230
1236
  stri2 += "{nr:0>2}{sep}{td:0>5}{sep} {inttime:0>5} {sep}".format(
1231
- nr=i + 1,
1237
+ nr=index + 1,
1232
1238
  sep=separator,
1233
1239
  td=td,
1234
1240
  inttime=inttime
@@ -1676,7 +1682,7 @@ def boatedit(fileName="my1x.txt"): # pragma: no cover
1676
1682
  ))
1677
1683
  if (strin != ""):
1678
1684
  try:
1679
- rg.spread = float(spread)
1685
+ rg.spread = float(strin)
1680
1686
  except ValueError:
1681
1687
  print("Not a valid number. Keeping original value")
1682
1688
  else:
@@ -1688,7 +1694,7 @@ def boatedit(fileName="my1x.txt"): # pragma: no cover
1688
1694
  ))
1689
1695
  if (strin != ""):
1690
1696
  try:
1691
- rg.span = float(span)
1697
+ rg.span = float(strin)
1692
1698
  except ValueError:
1693
1699
  print("Not a valid number. Keeping original value")
1694
1700
 
@@ -2178,6 +2184,67 @@ class rowingdata:
2178
2184
  """
2179
2185
 
2180
2186
  def __init__(self, *args, **kwargs):
2187
+ """Initialize a rowingdata object from a CSV file or pandas DataFrame.
2188
+
2189
+ This is the main class for reading and analyzing rowing workout data.
2190
+ It parses CSV files from various rowing ergometer and on-the-water
2191
+ rowing systems, performs data validation and enrichment, and provides
2192
+ methods for analysis, visualization, and export.
2193
+
2194
+ Parameters
2195
+ ----------
2196
+ csvfile : str, optional
2197
+ Path to the CSV file containing rowing data. Can be gzipped (.gz).
2198
+ If not provided, a DataFrame must be passed via the 'df' parameter.
2199
+ df : pandas.DataFrame, optional
2200
+ A pre-loaded DataFrame containing rowing data. If provided, csvfile
2201
+ is ignored.
2202
+ rower : rowingdata.rower, optional
2203
+ A rower object containing personal data (HR thresholds, FTP, etc.).
2204
+ Defaults to a new rower() instance with default values.
2205
+ rowtype : str, optional
2206
+ Type of rowing data ('Indoor Rower', 'OTW', etc.).
2207
+ Defaults to 'Indoor Rower'.
2208
+ absolutetimestamps : bool, optional
2209
+ If True, timestamps are kept as Unix time (seconds since 1970).
2210
+ If False (default), timestamps are relative to workout start.
2211
+ debug : bool, optional
2212
+ If True, enables debug output. Defaults to False.
2213
+
2214
+ Attributes
2215
+ ----------
2216
+ df : pandas.DataFrame
2217
+ The main data frame containing all rowing metrics.
2218
+ readfilename : str
2219
+ Name of the source file or 'rowing dataframe' if created from df.
2220
+ rwr : rowingdata.rower
2221
+ The rower object with personal training zones.
2222
+ rowtype : str
2223
+ Type of rowing data.
2224
+ empty : bool
2225
+ True if the DataFrame is empty.
2226
+ duration : float
2227
+ Total workout duration in seconds.
2228
+ dragfactor : float
2229
+ Drag factor from the rowing data (for ergometer data).
2230
+ stroke_count : int
2231
+ Total number of strokes in the workout.
2232
+
2233
+ Examples
2234
+ --------
2235
+ >>> # Load from CSV file
2236
+ >>> row = rowingdata(csvfile="workout.csv")
2237
+ >>> row.summary()
2238
+
2239
+ >>> # Load from DataFrame
2240
+ >>> import pandas as pd
2241
+ >>> df = pd.read_csv("workout.csv")
2242
+ >>> row = rowingdata(df=df)
2243
+
2244
+ >>> # With custom rower data
2245
+ >>> my_rower = rower(hrut2=140, hrmax=190, ftp=250)
2246
+ >>> row = rowingdata(csvfile="workout.csv", rower=my_rower)
2247
+ """
2181
2248
 
2182
2249
  if 'debug' in kwargs: # pragma: no cover
2183
2250
  debug = kwargs['debug']
@@ -3061,6 +3128,36 @@ class rowingdata:
3061
3128
  with open(fileName,'wb+') as f_out:
3062
3129
  f_out.write(bytes)
3063
3130
 
3131
+ def exporttofit(self, fileName, notes="Exported by Rowingdata",
3132
+ sport="rowing", use_developer_fields=True):
3133
+ """Export rowingdata to FIT format for Intervals.icu and other platforms.
3134
+
3135
+ Parameters
3136
+ ----------
3137
+ fileName : str
3138
+ Output file path (e.g. 'activity.fit')
3139
+ notes : str
3140
+ Activity notes
3141
+ sport : str
3142
+ Sport type: 'rowing', 'indoor_rowing', 'water', 'other'
3143
+ use_developer_fields : bool
3144
+ If True, include rowing-specific columns (DriveLength, StrokeDistance,
3145
+ etc.) when present. Requires fit-tool and Intervals.icu importer support.
3146
+ """
3147
+ if not self.empty:
3148
+ df = self.df.copy()
3149
+ res = make_cumvalues(df[' Horizontal (meters)'])
3150
+ df[' Horizontal (meters)'] = res[0]
3151
+ df['cum_dist'] = res[0]
3152
+ fitwrite.write_fit(
3153
+ fileName, df,
3154
+ row_date=self.rowdatetime.isoformat(),
3155
+ notes=notes, sport=sport,
3156
+ use_developer_fields=use_developer_fields
3157
+ )
3158
+ else: # pragma: no cover
3159
+ raise ValueError("Cannot export empty rowingdata session to FIT")
3160
+
3064
3161
  def split_by_intervals(self):
3065
3162
  """ Returns the work intervals as separate rowingdata objects"""
3066
3163
  if self.empty:
@@ -380,16 +380,35 @@ def tcxtodf3(path):
380
380
  extensions_element = trackpoint.find(".//{http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2}Extensions")
381
381
  if extensions_element is not None:
382
382
  # print child elements of extensions_element
383
- watts_node = extensions_element.find(".//{http://www.garmin.com/xmlschemas/ActivityExtension/v2}TPX/{http://www.garmin.com/xmlschemas/ActivityExtension/v2}Watts")
383
+ # looks like so
384
+ #<ns0:Extensions xmlns:ns0="http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2" xmlns:ns1="http://www.garmin.com/xmlschemas/ActivityExtension/v2">
385
+ #<ns1:ActivityTrackpointExtension>
386
+ # <ns0:Extensions>
387
+ # <ns0:Check>29</ns0:Check>
388
+ # <ns0:Bounce>1</ns0:Bounce>
389
+ # <ns0:Speed>0.23</ns0:Speed>
390
+ # <ns0:AvgSpeed>2.29</ns0:AvgSpeed>
391
+ # <ns0:Mps>0.00</ns0:Mps>
392
+ # </ns0:Extensions>
393
+ #</ns1:ActivityTrackpointExtension>
394
+ #</ns0:Extensions>
395
+ # extract Check and Watts from the extensions
396
+ watts_node = extensions_element.find(".//{http://www.garmin.com/xmlschemas/ActivityExtension/v2}ActivityTrackpointExtension/{http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2}Extensions/{http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2}Watts")
397
+ check_factor = extensions_element.find(".//{http://www.garmin.com/xmlschemas/ActivityExtension/v2}ActivityTrackpointExtension/{http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2}Extensions/{http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2}Check")
398
+
399
+ if watts_node is None:
400
+ watts_node = extensions_element.find(".//{http://www.garmin.com/xmlschemas/ActivityExtension/v2}TPX/{http://www.garmin.com/xmlschemas/ActivityExtension/v2}Watts")
384
401
 
385
402
  watts = float(clean_string(watts_node.text)) if watts_node is not None else 0
386
403
 
387
- check_factor = extensions_element.find(".//{http://www.garmin.com/xmlschemas/ActivityExtension/v2}TPX/{http://www.garmin.com/xmlschemas/ActivityExtension/v2}Check")
404
+ #check_factor = extensions_element.find(".//{http://www.garmin.com/xmlschemas/ActivityExtension/v2}TPX/{http://www.garmin.com/xmlschemas/ActivityExtension/v2}Check")
388
405
  try:
389
406
  check_factor = float(clean_string(check_factor.text)) if check_factor is not None else 0
390
407
  except ValueError:
391
408
  check_factor = 0
392
409
 
410
+
411
+
393
412
  speed_node = trackpoint.find(".//{http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2}Speed")
394
413
  speed = float(clean_string(speed_node.text)) if speed_node is not None else 0
395
414
 
@@ -1,8 +1,8 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: rowingdata
3
- Version: 3.6.26
3
+ Version: 3.7.1
4
4
  Summary: The rowingdata library to create colorful plots from CrewNerd, Painsled and other rowing data tools
5
- Home-page:
5
+ Home-page: https://github.com/sanderroosendaal/rowingdata
6
6
  Author: Sander Roosendaal
7
7
  Author-email: roosendaalsander@gmail.com
8
8
  License: MIT
@@ -15,6 +15,7 @@ Requires-Dist: scipy
15
15
  Requires-Dist: matplotlib
16
16
  Requires-Dist: pandas
17
17
  Requires-Dist: fitparse
18
+ Requires-Dist: fit-tool>=0.9.14
18
19
  Requires-Dist: arrow>=1.0.2
19
20
  Requires-Dist: python-dateutil
20
21
  Requires-Dist: docopt
@@ -63,6 +63,7 @@ rowingdata/ergdatatotcx.py
63
63
  rowingdata/ergstickplot.py
64
64
  rowingdata/ergstickplottime.py
65
65
  rowingdata/ergsticktotcx.py
66
+ rowingdata/fitwrite.py
66
67
  rowingdata/gpxtools.py
67
68
  rowingdata/gpxwrite.py
68
69
  rowingdata/konkatenaadje.py
@@ -128,6 +129,7 @@ testdata/boatcoach.csv
128
129
  testdata/boatcoach_fixed_distance.csv
129
130
  testdata/boatcoach_otw.csv
130
131
  testdata/correctedpainsled.csv
132
+ testdata/cottwich.csv
131
133
  testdata/coxmate.csv
132
134
  testdata/crewnerd_interval.csv
133
135
  testdata/crewnerddata.CSV
@@ -4,6 +4,7 @@ scipy
4
4
  matplotlib
5
5
  pandas
6
6
  fitparse
7
+ fit-tool>=0.9.14
7
8
  arrow>=1.0.2
8
9
  python-dateutil
9
10
  docopt