rowingdata 3.6.8__py3-none-any.whl

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 (49) hide show
  1. rowingdata/__init__.py +2 -0
  2. rowingdata/__main__.py +2 -0
  3. rowingdata/boatedit.py +15 -0
  4. rowingdata/checkdatafiles.py +216 -0
  5. rowingdata/copystats.py +22 -0
  6. rowingdata/crewnerdplot.py +37 -0
  7. rowingdata/crewnerdplottime.py +37 -0
  8. rowingdata/csvparsers.py +3114 -0
  9. rowingdata/ergdataplot.py +31 -0
  10. rowingdata/ergdataplottime.py +31 -0
  11. rowingdata/ergdatatotcx.py +32 -0
  12. rowingdata/ergstickplot.py +31 -0
  13. rowingdata/ergstickplottime.py +32 -0
  14. rowingdata/ergsticktotcx.py +32 -0
  15. rowingdata/example.csv +5171 -0
  16. rowingdata/gpxtools.py +70 -0
  17. rowingdata/gpxwrite.py +151 -0
  18. rowingdata/konkatenaadje.py +19 -0
  19. rowingdata/laptesting.py +293 -0
  20. rowingdata/obsolete.py +654 -0
  21. rowingdata/otherparsers.py +718 -0
  22. rowingdata/painsled_desktop_plot.py +30 -0
  23. rowingdata/painsled_desktop_plottime.py +29 -0
  24. rowingdata/painsled_desktop_toc2.py +30 -0
  25. rowingdata/painsledplot.py +27 -0
  26. rowingdata/painsledplottime.py +27 -0
  27. rowingdata/painsledtoc2.py +23 -0
  28. rowingdata/roweredit.py +15 -0
  29. rowingdata/rowingdata.py +6941 -0
  30. rowingdata/rowproplot.py +31 -0
  31. rowingdata/rowproplottime.py +31 -0
  32. rowingdata/speedcoachplot.py +31 -0
  33. rowingdata/speedcoachplottime.py +31 -0
  34. rowingdata/speedcoachtoc2.py +36 -0
  35. rowingdata/tcxplot.py +38 -0
  36. rowingdata/tcxplot_nogeo.py +38 -0
  37. rowingdata/tcxplottime.py +33 -0
  38. rowingdata/tcxplottime_nogeo.py +33 -0
  39. rowingdata/tcxtoc2.py +30 -0
  40. rowingdata/tcxtools.py +417 -0
  41. rowingdata/trainingparser.py +302 -0
  42. rowingdata/utils.py +135 -0
  43. rowingdata/windcorrected.py +48 -0
  44. rowingdata/writetcx.py +312 -0
  45. rowingdata-3.6.8.dist-info/LICENSE +21 -0
  46. rowingdata-3.6.8.dist-info/METADATA +1149 -0
  47. rowingdata-3.6.8.dist-info/RECORD +49 -0
  48. rowingdata-3.6.8.dist-info/WHEEL +5 -0
  49. rowingdata-3.6.8.dist-info/top_level.txt +1 -0
@@ -0,0 +1,3114 @@
1
+ # pylint: disable=C0103, C0303
2
+ from __future__ import absolute_import
3
+ #from builtins import (bytes, str, open, super, range,
4
+ # zip, round, input, int, pow, object)
5
+ import os
6
+ import io
7
+ import csv
8
+ import gzip
9
+ import zipfile
10
+ import re
11
+ import datetime
12
+ import pytz
13
+ import arrow
14
+ import iso8601
15
+ import traceback
16
+ import shutil
17
+ import numpy as np
18
+ import pandas as pd
19
+ import codecs
20
+ from pandas.core.indexing import IndexingError
21
+
22
+ try:
23
+ from StringIO import StringIO
24
+ except ImportError:
25
+ from io import StringIO
26
+
27
+ from pandas import Series, DataFrame
28
+ from dateutil import parser
29
+
30
+ from timezonefinder import TimezoneFinder
31
+ from lxml import objectify,etree
32
+ from fitparse import FitFile
33
+
34
+ try:
35
+ from .utils import (
36
+ totimestamp, format_pace, format_time,
37
+ )
38
+
39
+ from .tcxtools import strip_control_characters
40
+ except (ValueError,ImportError): # pragma: no cover
41
+ from rowingdata.utils import (
42
+ totimestamp, format_pace, format_time,
43
+ )
44
+
45
+ from rowingdata.tcxtools import strip_control_characters
46
+
47
+
48
+ import six
49
+ from six.moves import range
50
+ from six.moves import zip
51
+
52
+ import sys
53
+ if sys.version_info[0]<=2: # pragma: no cover
54
+ pythonversion = 2
55
+ readmode = 'r'
56
+ readmodebin = 'rb'
57
+ else:
58
+ readmode = 'rt'
59
+ readmodebin = 'rt'
60
+ pythonversion = 3
61
+ from io import open
62
+
63
+
64
+ # we're going to plot SI units - convert pound force to Newton
65
+ lbstoN = 4.44822
66
+
67
+ def clean_nan(x):
68
+ for i in range(len(x) - 2):
69
+ if np.isnan(x[i + 1]):
70
+ if x[i + 2] > x[i]:
71
+ x[i + 1] = 0.5 * (x[i] + x[i + 2])
72
+ if x[i + 2] < x[i]:
73
+ x[i + 1] = 0
74
+
75
+ return x
76
+
77
+ def make_converter(convertlistbase,df):
78
+ converters = {}
79
+ for key in convertlistbase:
80
+ try:
81
+ try:
82
+ values = df[key].apply(
83
+ lambda x: float(x.replace('.', '').replace(',', '.'))
84
+ )
85
+ converters[key] = lambda x: float(x.replace('.', '').replace(',', '.'))
86
+ except AttributeError:
87
+ pass
88
+ except KeyError: # pragma: no cover
89
+ pass
90
+
91
+ return converters
92
+
93
+
94
+ def flexistrptime(inttime):
95
+
96
+ try:
97
+ t = datetime.datetime.strptime(inttime, "%H:%M:%S.%f")
98
+ except ValueError:
99
+ try:
100
+ t = datetime.datetime.strptime(inttime, "%M:%S")
101
+ except ValueError:
102
+ try:
103
+ t = datetime.datetime.strptime(inttime, "%H:%M:%S")
104
+ except ValueError:
105
+ try:
106
+ t = datetime.datetime.strptime(inttime, "%M:%S.%f")
107
+ except ValueError: # pragma: no cover
108
+ t = datetime.datetime.utcnow()
109
+
110
+ return t
111
+
112
+ def flexistrftime(t):
113
+ h = t.hour
114
+ m = t.minute
115
+ s = t.second
116
+ us = t.microsecond
117
+
118
+ second = s + us / 1.e6
119
+ m = m + 60 * h
120
+ string = "{m:0>2}:{s:0>4.1f}".format(
121
+ m=m,
122
+ s=second
123
+ )
124
+
125
+ return string
126
+
127
+ def csvtests(s):
128
+ # get first and 7th line of file
129
+
130
+ try:
131
+ firstline = s[0]
132
+ except IndexError: # pragma: no cover
133
+ firstline = ''
134
+
135
+ try:
136
+ secondline = s[1]
137
+ except IndexError: # pragma: no cover
138
+ secondline = ''
139
+
140
+ try:
141
+ thirdline = s[2]
142
+ except IndexError: # pragma: no cover
143
+ thirdline = ''
144
+
145
+ try:
146
+ fourthline = s[3]
147
+ except IndexError: # pragma: no cover
148
+ fourthline = ''
149
+
150
+ try:
151
+ seventhline = s[6]
152
+ except IndexError: # pragma: no cover
153
+ seventhline = ''
154
+
155
+ try:
156
+ ninthline = s[8]
157
+ except IndexError: # pragma: no cover
158
+ ninthline = ''
159
+
160
+ if 'CatchDelay' in thirdline:
161
+ return 'quiskesummary'
162
+
163
+ if 'Potential Split' in firstline:
164
+ return 'hero'
165
+
166
+ if 'timestamp' in firstline and 'InstaSpeed' in firstline:
167
+ return 'nklinklogbook'
168
+
169
+
170
+ if 'RitmoTime' in firstline:
171
+ return 'ritmotime'
172
+
173
+ if 'Quiske' in firstline:
174
+ return 'quiske'
175
+
176
+ if 'RowDate' in firstline: # pragma: no cover
177
+ return 'rowprolog'
178
+
179
+ if 'Workout Name' in firstline:
180
+ return 'c2log'
181
+
182
+ if 'Stroke (#)' in firstline and 'Work (J)' in firstline:
183
+ return 'smartrow'
184
+
185
+ if 'Concept2 Utility' in firstline: # pragma: no cover
186
+ return 'c2log'
187
+
188
+ if 'Concept2' in firstline: # pragma: no cover
189
+ return 'c2log'
190
+
191
+ if 'Workout #' in firstline: # pragma: no cover
192
+ return 'c2log'
193
+
194
+ if 'Activity Type' in firstline and 'Date' in firstline: # pragma: no cover
195
+ return 'c2log'
196
+
197
+ if 'Avg Watts' in firstline: # pragma: no cover
198
+ return 'c2log'
199
+
200
+ if 'Avg Speed (IMP)' in firstline:
201
+ return 'speedcoach2'
202
+
203
+ if 'LiNK' in ninthline:
204
+ return 'speedcoach2'
205
+
206
+ if 'SpeedCoach GPS Pro' in fourthline: # pragma: no cover
207
+ return 'speedcoach2'
208
+
209
+ if 'SpeedCoach GPS' in fourthline: # pragma: no cover
210
+ return 'speedcoach2'
211
+
212
+ if 'SpeedCoach GPS2' in fourthline: # pragma: no cover
213
+ return 'speedcoach2'
214
+
215
+ if 'SpeedCoach GPS Pro' in thirdline: # pragma: no cover
216
+ return 'speedcoach2'
217
+
218
+ if 'Practice Elapsed Time (s)' in firstline:
219
+ return 'mystery'
220
+
221
+ if 'Mike' in firstline and 'process' in firstline: # pragma: no cover
222
+ return 'bcmike'
223
+
224
+ if 'Club' in firstline and 'workoutType' in secondline:
225
+ return 'boatcoach'
226
+
227
+ if 'stroke500MPace' in firstline:
228
+ return 'boatcoach'
229
+
230
+ if 'Club' in secondline and 'Piece Stroke Count' in thirdline:
231
+ return 'boatcoachotw'
232
+
233
+ if 'Club' in firstline and 'Piece Stroke Count' in secondline: # pragma: no cover
234
+ return 'boatcoachotw'
235
+
236
+ if 'peak_force_pos' in firstline:
237
+ return 'rowperfect3'
238
+
239
+ if 'Hair' in seventhline:
240
+ return 'rp'
241
+
242
+ if 'smo2' in thirdline: # pragma: no cover
243
+ return 'humon'
244
+
245
+ if 'Total elapsed time (s)' in firstline:
246
+ return 'ergstick'
247
+ if 'Total elapsed time' in firstline:
248
+ return 'ergstick'
249
+
250
+ if 'Stroke Number' and 'Time (seconds)' in firstline:
251
+ return 'ergdata'
252
+
253
+ if 'Number' in firstline and 'Cal/Hr' in firstline: # pragma: no cover
254
+ return 'ergdata'
255
+
256
+ if 'Cadence (stokes/min)' in firstline:
257
+ return 'csv'
258
+
259
+ if ' DriveTime (ms)' in firstline:
260
+ return 'csv'
261
+
262
+ if 'ElapsedTime (sec)' in firstline: # pragma: no cover
263
+ return 'csv'
264
+
265
+ if 'HR' in firstline and 'Interval' in firstline and 'Avg HR' not in firstline:
266
+ return 'speedcoach'
267
+
268
+ if 'stroke.REVISION' in firstline:
269
+ return 'painsleddesktop'
270
+
271
+ if 'Date' in firstline and 'Latitude' in firstline and 'Heart rate' in firstline:
272
+ return 'kinomap'
273
+
274
+ if 'Cover' in firstline:
275
+ return 'coxmate'
276
+
277
+ if '500m Split (secs)' in firstline and 'Force Curve Data Points (Newtons)' in firstline:
278
+ return 'eth' # it's unknown but it was first submitted by a student from ETH Zurich
279
+
280
+ return 'unknown' # pragma: no cover
281
+
282
+ def get_file_type(f):
283
+ filename,extension = os.path.splitext(f)
284
+ extension = extension.lower()
285
+
286
+ if extension == '.xls': # pragma: no cover
287
+ return 'xls'
288
+ if extension == '.kml': # pragma: no cover
289
+ return 'kml'
290
+ if extension == '.txt': # pragma: no cover
291
+ if os.path.basename(f)[0:3].lower() == 'att':
292
+ return 'att'
293
+ if extension in ['.jpg','.jpeg','.tiff','.png','.gif','.bmp']: # pragma: no cover
294
+ return 'imageformat'
295
+ if extension in ['.json']: # pragma: no cover
296
+ return 'json'
297
+ if extension == '.gz':
298
+ filename,extension = os.path.splitext(filename)
299
+ if extension == '.fit':
300
+ newfile = 'temp.fit'
301
+ with gzip.open(f,'rb') as fop:
302
+ with open(newfile,'wb') as f_out:
303
+ shutil.copyfileobj(fop, f_out)
304
+
305
+ try:
306
+ FitFile(newfile, check_crc=False).parse()
307
+ return 'fit'
308
+ except: # pragma: no cover
309
+ return 'unknown'
310
+
311
+ return 'fit' # pragma: no cover
312
+ if extension == '.tcx':
313
+ try:
314
+ #p = etree.XMLParser(recover=True)
315
+ p = etree.XMLParser()
316
+ tree = etree.parse(f,parser=p)
317
+ root = tree.getroot()
318
+ return 'tcx'
319
+ except: # pragma: no cover
320
+ try:
321
+ with open(path, 'r') as fop:
322
+ input = fop.read()
323
+ input = strip_control_characters(input)
324
+ with open('temp_xml.tcx','w') as fout:
325
+ fout.write(input)
326
+
327
+ p = etree.XMLParser(recover=True)
328
+ tree = etree.parse('temp_xml.tcx',parser=p)
329
+ os.remove('temp_xml.tcx')
330
+ return 'tcx'
331
+ except:
332
+ return 'unknown'
333
+ if extension == '.gpx': # pragma: no cover
334
+ try:
335
+ tree = etree.parse(f)
336
+ root = tree.getroot()
337
+ return 'gpx'
338
+ except:
339
+ return 'unknown'
340
+
341
+ with gzip.open(f, readmode) as fop:
342
+ try:
343
+ if extension == '.csv':
344
+ s = fop.read().replace('\r\n','\n').replace('\r','\n').split('\n')
345
+ return csvtests(s)
346
+
347
+ except IOError: # pragma: no cover
348
+ return 'notgzip'
349
+ if extension == '.csv':
350
+ linecount,isbinary = get_file_linecount(f)
351
+ if linecount <= 2: # pragma: no cover
352
+ return 'nostrokes'
353
+
354
+ if isbinary: # pragma: no cover
355
+ with open(f,readmodebin) as fop:
356
+ s = fop.read().replace('\r\n','\n').replace('\r','\n').split('\n')
357
+ else:
358
+ with open(f, readmode) as fop:
359
+ s = fop.read().replace('\r\n','\n').replace('\r','\n').split('\n')
360
+
361
+ return csvtests(s)
362
+
363
+ if extension == '.tcx':
364
+ try:
365
+ p = etree.XMLParser(recover=True)
366
+ tree = etree.parse(f,parser=p)
367
+ root = tree.getroot()
368
+ return 'tcx'
369
+ except:
370
+ try:
371
+ with open(f, 'r') as fop:
372
+ input = fop.read()
373
+ input = strip_control_characters(input)
374
+ with open('temp_xml.tcx','w') as ftemp:
375
+ ftemp.write(input)
376
+
377
+ p = etree.XMLParser(recover=True)
378
+
379
+ tree = etree.parse('temp_xml.tcx',parser=p)
380
+ os.remove('temp_xml.tcx')
381
+ return 'tcx'
382
+ except:
383
+ return 'unknown'
384
+ if extension == '.gpx': # pragma: no cover
385
+ try:
386
+ tree = etree.parse(f)
387
+ root = tree.getroot()
388
+ return 'gpx'
389
+ except:
390
+ return 'unknown'
391
+
392
+ if extension == '.fit':
393
+ try:
394
+ FitFile(f, check_crc=False).parse()
395
+ except: # pragma: no cover
396
+ return 'unknown'
397
+
398
+ return 'fit'
399
+
400
+ if extension == '.zip': # pragma: no cover
401
+ try:
402
+ z = zipfile.ZipFile(f)
403
+ f2 = z.extract(z.namelist()[0])
404
+ tp = get_file_type(f2)
405
+ os.remove(f2)
406
+ return 'zip', f2, tp
407
+ except:
408
+ return 'unknown'
409
+
410
+ return 'unknown'
411
+
412
+ def get_file_linecount(f):
413
+ # extension = f[-3:].lower()
414
+ extension = os.path.splitext(f)[1].lower()
415
+ isbinary = False
416
+ if extension == '.gz': # pragma: no cover
417
+ with gzip.open(f,'rb') as fop:
418
+ count = sum(1 for line in fop if line.rstrip('\n'))
419
+ if count <= 2:
420
+ # test for \r
421
+ with gzip.open(f,readmodebin) as fop:
422
+ s = fop.read().replace('\r\n','\n').replace('\r','\n').split('\n')
423
+ count = len(s)
424
+
425
+ else:
426
+ with open(f, 'r') as fop:
427
+ try:
428
+ count = sum(1 for line in fop if line.rstrip('\n'))
429
+ except: # pragma: no cover
430
+ return 0,False
431
+ if count <= 2: # pragma: no cover
432
+ # test for \r
433
+ with open(f,readmodebin) as fop:
434
+ isbinary = True
435
+ s = fop.read().replace('\r\n','\n').replace('\r','\n').split('\n')
436
+ count = len(s)
437
+
438
+ return count,isbinary
439
+
440
+ def get_file_line(linenr, f, isbinary=False):
441
+ line = ''
442
+ extension = os.path.splitext(f)[1].lower()
443
+ # extension = f[-3:].lower()
444
+ if extension == '.gz':
445
+ with gzip.open(f, readmode) as fop:
446
+ s = fop.read().replace('\r\n','\n').replace('\r','\n').split('\n')
447
+ else:
448
+ with open(f, readmodebin) as fop:
449
+ s = fop.read().replace('\r\n','\n').replace('\r','\n').split('\n')
450
+
451
+ return s[linenr-1]
452
+
453
+
454
+ def get_separator(linenr, f):
455
+ line = ''
456
+ extension = os.path.splitext(f)[1].lower()
457
+ # extension = f[-3:].lower()
458
+ if extension == '.gz':
459
+ with gzip.open(f, readmode) as fop:
460
+ for i in range(linenr):
461
+ line = fop.readline()
462
+
463
+ sep = ','
464
+ sniffer = csv.Sniffer()
465
+ sep = sniffer.sniff(line).delimiter
466
+ else:
467
+ with open(f, 'r') as fop:
468
+ for i in range(linenr):
469
+ line = fop.readline()
470
+
471
+ sep = ','
472
+ sniffer = csv.Sniffer()
473
+ sep = sniffer.sniff(line).delimiter
474
+
475
+ return sep
476
+
477
+ def empower_bug_correction(oarlength,inboard,a,b):
478
+ f = (oarlength-inboard-b)/(oarlength-inboard-a)
479
+
480
+ return f
481
+
482
+ def getoarlength(line):
483
+ l = float(line.split(',')[-1])
484
+
485
+ return l
486
+
487
+ def getinboard(line):
488
+ inboard = float(line.split(',')[-1])
489
+ return inboard
490
+
491
+ def getfirmware(line):
492
+ l = line.lower().split(',')
493
+ try:
494
+ firmware = l[l.index("firmware version:")+1]
495
+ except ValueError: # pragma: no cover
496
+ firmware = ''
497
+
498
+ return firmware
499
+
500
+ def get_empower_rigging(f):
501
+ oarlength = 289.
502
+ inboard = 88.
503
+ line = '1'
504
+ try:
505
+ with open(f, readmode) as fop:
506
+ for line in fop:
507
+ if 'Oar Length' in line:
508
+ try:
509
+ oarlength = getoarlength(line)
510
+ except ValueError:
511
+ return None,None
512
+ if 'Inboard' in line:
513
+ try:
514
+ inboard = getinboard(line)
515
+ except ValueError: # pragma: no cover
516
+ return None,None
517
+ except (UnicodeDecodeError,ValueError): # pragma: no cover
518
+ with gzip.open(f, readmode) as fop:
519
+ for line in fop:
520
+ if 'Oar Length' in line:
521
+ try:
522
+ oarlength = getoarlength(line)
523
+ except ValueError:
524
+ return None,None
525
+ if 'Inboard' in line: # pragma: no cover
526
+ try:
527
+ inboard = getinboard(line)
528
+ except ValueError:
529
+ return None,None
530
+
531
+
532
+
533
+ return oarlength / 100., inboard / 100.
534
+
535
+ def get_empower_firmware(f):
536
+ firmware = ''
537
+ try:
538
+ with open(f,readmode) as fop:
539
+ for line in fop:
540
+ if 'firmware' in line.lower() and 'oar' in line.lower():
541
+ firmware = getfirmware(line)
542
+ except (IndexError,UnicodeDecodeError):
543
+ with gzip.open(f,readmode) as fop:
544
+ for line in fop:
545
+ if 'firmware' in line.lower() and 'oar' in line.lower():
546
+ firmware = getfirmware(line)
547
+
548
+
549
+
550
+ try:
551
+ firmware = float(firmware)
552
+ except ValueError:
553
+ firmware = None
554
+
555
+ return firmware
556
+
557
+ def skip_variable_footer(f):
558
+ counter = 0
559
+ counter2 = 0
560
+
561
+ extension = os.path.splitext(f)[1].lower()
562
+ # extension = f[-3:].lower()
563
+ if extension == '.gz':
564
+ fop = gzip.open(f,readmode)
565
+ else:
566
+ fop = open(f, 'r')
567
+
568
+ for line in fop:
569
+ if line.startswith('Type') and counter > 15:
570
+ counter2 = counter
571
+ counter += 1
572
+ else:
573
+ counter += 1
574
+
575
+ fop.close()
576
+
577
+ return counter - counter2 + 1
578
+
579
+ def get_rowpro_footer(f, converters={}):
580
+ counter = 0
581
+ counter2 = 0
582
+
583
+
584
+ extension = os.path.splitext(f)[1].lower()
585
+ # extension = f[-3:].lower()
586
+ if extension == '.gz':
587
+ fop = gzip.open(f,readmode)
588
+ else:
589
+ fop = open(f, 'r')
590
+
591
+ for line in fop:
592
+ if line.startswith('Type') and counter > 15:
593
+ counter2 = counter
594
+ counter += 1
595
+ else:
596
+ counter += 1
597
+
598
+ fop.close()
599
+
600
+ return pd.read_csv(f, skiprows=counter2,
601
+ converters=converters,
602
+ engine='python',
603
+ sep=None, index_col=False)
604
+
605
+
606
+ def skip_variable_header(f):
607
+ counter = 0
608
+ counter2 = 0
609
+ sessionc = -2
610
+ summaryc = -2
611
+ extension = os.path.splitext(f)[1].lower()
612
+ # extension = f[-3:].lower()
613
+ firmware = ''
614
+ if extension == '.gz':
615
+ with gzip.open(f,readmode) as fop:
616
+ s = fop.read().replace('\r\n','\n').replace('\r','\n').split('\n')
617
+ else:
618
+ with open(f,readmodebin) as fop:
619
+ s = fop.read().replace('\r\n','\n').replace('\r','\n').split('\n')
620
+
621
+
622
+ summaryfound = False
623
+ for line in s:
624
+ if line.startswith('Session Summary'):
625
+ sessionc = counter
626
+ summaryfound = True
627
+ if line.startswith('Interval Summaries'):
628
+ summaryc = counter
629
+ if 'firmware' in line.lower() and 'oar' in line.lower():
630
+ firmware = getfirmware(line)
631
+ if line.startswith('Session Detail Data') or line.startswith('Per-Stroke Data'):
632
+ counter2 = counter
633
+ else:
634
+ counter += 1
635
+
636
+
637
+
638
+ # test for blank line
639
+ l = s[counter2+1]
640
+ # l = get_file_line(counter2 + 2, f)
641
+ if 'Interval' in l:
642
+ counter2 = counter2 - 1
643
+ summaryc = summaryc - 1
644
+ blanklines = 0
645
+ else:
646
+ blanklines = 1
647
+
648
+ return counter2 + 2, summaryc + 2, blanklines, sessionc + 2
649
+
650
+ def ritmo_variable_header(f):
651
+ counter = 0
652
+ extension = os.path.splitext(f)[1].lower()
653
+ # extension = f[-3:].lower()
654
+ if extension == '.gz': # pragma: no cover
655
+ fop = gzip.open(f,readmode)
656
+ else:
657
+ fop = open(f, 'r')
658
+
659
+ for line in fop:
660
+ if line.startswith('#'):
661
+ counter += 1
662
+ else:
663
+ fop.close()
664
+ return counter
665
+
666
+ return counter # pragma: no cover
667
+
668
+ def bc_variable_header(f):
669
+ counter = 0
670
+ extension = os.path.splitext(f)[1].lower()
671
+ # extension = f[-3:].lower()
672
+ if extension == '.gz':
673
+ fop = gzip.open(f,readmode)
674
+ else:
675
+ fop = open(f, 'r')
676
+
677
+
678
+ for line in fop:
679
+ if line.startswith('Workout'):
680
+ fop.close()
681
+ return counter+1
682
+ else:
683
+ counter += 1
684
+
685
+ fop.close() # pragma: no cover
686
+
687
+ return 0 # pragma: no cover
688
+
689
+ def make_cumvalues_array(xvalues,doequal=False):
690
+ """ Takes a Pandas dataframe with one column as input value.
691
+ Tries to create a cumulative series.
692
+
693
+ """
694
+
695
+ try:
696
+ newvalues = 0.0 * xvalues
697
+ except TypeError: # pragma: no cover
698
+ return [xvalues,0]
699
+
700
+ dx = np.diff(xvalues)
701
+ dxpos = dx
702
+ nrsteps = len(dxpos[dxpos < 0])
703
+ lapidx = np.append(0, np.cumsum((-dx + abs(dx)) / (-2 * dx)))
704
+ if doequal:
705
+ lapidx[0] = 0
706
+ cntr = 0
707
+ for i in range(len(dx)-1):
708
+ if dx[i+1] <= 0:
709
+ cntr += 1
710
+ lapidx[i+1] = cntr
711
+ if nrsteps > 0:
712
+ indexes = np.where(dxpos < 0)
713
+ for index in indexes:
714
+ dxpos[index] = xvalues[index + 1]
715
+ newvalues = np.append(0, np.cumsum(dxpos)) + xvalues[0]
716
+ else:
717
+ newvalues = xvalues
718
+
719
+ return [newvalues, abs(lapidx)]
720
+
721
+
722
+ def make_cumvalues(xvalues):
723
+ """ Takes a Pandas dataframe with one column as input value.
724
+ Tries to create a cumulative series.
725
+
726
+ """
727
+
728
+ newvalues = 0.0 * xvalues
729
+ dx = xvalues.diff()
730
+ dxpos = dx
731
+ mask = -xvalues.diff() > 0.9 * xvalues
732
+ nrsteps = len(dx.loc[mask])
733
+ lapidx = np.cumsum((-dx + abs(dx)) / (-2 * dx))
734
+ lapidx = lapidx.replace(to_replace=np.nan,value=0)
735
+ test = len(lapidx.loc[lapidx.diff() < 0])
736
+ if test != 0:
737
+ lapidx = np.cumsum((-dx + abs(dx)) / (-2 * dx))
738
+ lapidx = lapidx.ffill()
739
+ lapidx.loc[0] = 0
740
+ if nrsteps > 0:
741
+ dxpos[mask] = xvalues[mask]
742
+ newvalues = np.cumsum(dxpos) + xvalues.iloc[0]
743
+ newvalues.iloc[0] = xvalues.iloc[0]
744
+ else:
745
+ newvalues = xvalues
746
+
747
+ newvalues = newvalues.replace([-np.inf, np.inf], np.nan)
748
+
749
+
750
+ newvalues.bfill(inplace=True)
751
+ newvalues.bfill( inplace=True)
752
+ lapidx.bfill( inplace=True)
753
+
754
+ return [newvalues, lapidx]
755
+
756
+ def timestrtosecs(string):
757
+ dt = parser.parse(string, fuzzy=True)
758
+ secs = 3600 * dt.hour + 60 * dt.minute + dt.second
759
+
760
+ return secs
761
+
762
+ def speedtopace(v, unit='ms'):
763
+ if unit == 'kmh':
764
+ v = v * 1000 / 3600.
765
+ if v > 0:
766
+ p = 500. / v
767
+ else:
768
+ p = np.nan
769
+
770
+ return p
771
+
772
+ def timestrtosecs2(timestring, unknown=0):
773
+ try:
774
+ h, m, s = timestring.split(':')
775
+ sval = 3600 * int(h) + 60. * int(m) + float(s)
776
+ except ValueError:
777
+ try:
778
+ m, s = timestring.split(':')
779
+ sval = 60. * int(m) + float(s)
780
+ except ValueError:
781
+ sval = unknown
782
+
783
+ return sval
784
+
785
+
786
+ def getcol(df, column='TimeStamp (sec)'):
787
+ if column:
788
+ try:
789
+ return df[column]
790
+ except KeyError:
791
+ pass
792
+
793
+ l = len(df.index)
794
+ return Series(np.zeros(l))
795
+
796
+
797
+ class CSVParser(object):
798
+ """ Parser for reading CSV files created by Painsled
799
+
800
+ """
801
+
802
+ def __init__(self, *args, **kwargs):
803
+ if args:
804
+ csvfile = args[0]
805
+ else:
806
+ csvfile = kwargs.pop('csvfile', 'test.csv')
807
+
808
+ skiprows = kwargs.pop('skiprows', 0)
809
+ usecols = kwargs.pop('usecols', None)
810
+ sep = kwargs.pop('sep', ',')
811
+ engine = kwargs.pop('engine', 'c')
812
+ skipfooter = kwargs.pop('skipfooter', 0)
813
+ converters = kwargs.pop('converters', None)
814
+
815
+ self.csvfile = csvfile
816
+
817
+ if engine == 'python':
818
+ self.df = pd.read_csv(
819
+ csvfile, skiprows=skiprows, usecols=usecols,
820
+ sep=sep, engine=engine, skipfooter=skipfooter,
821
+ converters=converters, index_col=False,
822
+ compression='infer',
823
+ )
824
+ else:
825
+ self.df = pd.read_csv(
826
+ csvfile, skiprows=skiprows, usecols=usecols,
827
+ sep=sep, engine=engine, skipfooter=skipfooter,
828
+ converters=converters, index_col=False,
829
+ compression='infer',
830
+ #error_bad_lines = False
831
+ )
832
+
833
+
834
+ self.df = self.df.ffill()
835
+
836
+ self.defaultcolumnnames = [
837
+ 'TimeStamp (sec)',
838
+ ' Horizontal (meters)',
839
+ ' Cadence (stokes/min)',
840
+ ' HRCur (bpm)',
841
+ ' Stroke500mPace (sec/500m)',
842
+ ' Power (watts)',
843
+ ' DriveLength (meters)',
844
+ ' StrokeDistance (meters)',
845
+ ' DriveTime (ms)',
846
+ ' DragFactor',
847
+ ' StrokeRecoveryTime (ms)',
848
+ ' AverageDriveForce (lbs)',
849
+ ' PeakDriveForce (lbs)',
850
+ ' lapIdx',
851
+ ' ElapsedTime (sec)',
852
+ ' latitude',
853
+ ' longitude',
854
+ ]
855
+
856
+ try:
857
+ x = self.df['TimeStamp (sec)']
858
+ except KeyError:
859
+ cols = self.df.columns
860
+ for col in cols:
861
+ if 'TimeStamp ' in col: # pragma: no cover
862
+ self.df['TimeStamp (sec)'] = self.df[col]
863
+
864
+ self.columns = {c: c for c in self.defaultcolumnnames}
865
+
866
+ def to_standard(self):
867
+ inverted = {value: key for key, value in six.iteritems(self.columns)}
868
+
869
+ self.df.rename(columns=inverted, inplace=True)
870
+ self.columns = {c: c for c in self.defaultcolumnnames}
871
+
872
+ def time_values(self, *args, **kwargs):
873
+ timecolumn = kwargs.pop('timecolumn', 'TimeStamp (sec)')
874
+ unixtimes = self.df[timecolumn]
875
+
876
+ return unixtimes
877
+
878
+ def write_csv(self, *args, **kwargs):
879
+ if self.df.empty: # pragma: no cover
880
+ return None
881
+
882
+ isgzip = kwargs.pop('gzip', False)
883
+ writeFile = args[0]
884
+
885
+ # defaultmapping ={c:c for c in self.defaultcolumnnames}
886
+ self.columns = kwargs.pop('columns', self.columns)
887
+
888
+ unixtimes = self.time_values(
889
+ timecolumn=self.columns['TimeStamp (sec)'])
890
+ self.df[self.columns['TimeStamp (sec)']] = unixtimes
891
+ self.df[
892
+ self.columns[' ElapsedTime (sec)']
893
+ ] = unixtimes - unixtimes.iloc[0]
894
+ # Default calculations
895
+ pace = self.df[
896
+ self.columns[' Stroke500mPace (sec/500m)']].replace(0, 300)
897
+ self.df[self.columns[' Stroke500mPace (sec/500m)']] = pace
898
+
899
+ datadict = {name: getcol(self.df, self.columns[name])
900
+ for name in self.columns}
901
+
902
+
903
+ # Create data frame with all necessary data to write to csv
904
+ data = DataFrame(datadict)
905
+
906
+ data = data.sort_values(by='TimeStamp (sec)', ascending=True)
907
+ data = data.ffill()
908
+
909
+ # drop all-zero columns
910
+ for c in data.columns:
911
+ if (data[c] == 0).any() and data[c].mean() == 0:
912
+ data = data.drop(c, axis=1)
913
+
914
+ if isgzip: # pragma: no cover
915
+ return data.to_csv(writeFile + '.gz', index_label='index',
916
+ compression='gzip')
917
+ else:
918
+ return data.to_csv(writeFile, index_label='index')
919
+
920
+ # Parsing ETH files
921
+ class ETHParser(CSVParser):
922
+ def __init__(self, *args, **kwargs):
923
+ if args:
924
+ csvfile = args[0]
925
+ else: # pragma: no cover
926
+ csvfile = kwargs['csvfile']
927
+
928
+ super(ETHParser, self).__init__(*args, **kwargs)
929
+
930
+ self.cols = [
931
+ '',
932
+ 'Distance (meters)',
933
+ 'Stroke Rate (s/m)',
934
+ 'Heart Rate (bpm)',
935
+ '500m Split (secs)',
936
+ 'Power (Watts)',
937
+ 'Drive Length (meters)',
938
+ '',
939
+ 'Drive Time (secs)',
940
+ '',
941
+ '',
942
+ 'Average Drive Force (Newtons)',
943
+ 'Peak Force (Newtons)',
944
+ '',
945
+ 'Time (secs)',
946
+ '',
947
+ '',
948
+ ]
949
+
950
+ self.cols = [b if a == '' else a
951
+ for a,b in zip(self.cols, self.defaultcolumnnames)]
952
+ self.columns = dict(list(zip(self.defaultcolumnnames, self.cols)))
953
+
954
+ # calculations
955
+ self.df[self.columns[' DriveTime (ms)']] *= 1000.
956
+
957
+ startdatetime = datetime.datetime.utcnow()
958
+ elapsed = self.df[self.columns[' ElapsedTime (sec)']]
959
+ starttimeunix = arrow.get(startdatetime).timestamp()
960
+
961
+ unixtimes = starttimeunix+elapsed
962
+ self.df[self.columns['TimeStamp (sec)']] = unixtimes
963
+ self.df[self.columns[' ElapsedTime (sec)']] = unixtimes-starttimeunix
964
+
965
+ self.to_standard()
966
+
967
+
968
+ # Parsing CSV files from Humon
969
+ class HumonParser(CSVParser): # pragma: no cover
970
+ def __init__(self, *args, **kwargs):
971
+ if args:
972
+ csvfile = args[0]
973
+ else:
974
+ csvfile = kwargs['csvfile']
975
+
976
+ skiprows = 2
977
+ kwargs['skiprows'] = skiprows
978
+
979
+ super(HumonParser, self).__init__(*args, **kwargs)
980
+
981
+ self.cols = [
982
+ 'Time [seconds]',
983
+ 'distance [meters]',
984
+ '',
985
+ 'heartRate [bpm]',
986
+ 'speed [meters/sec]',
987
+ '',
988
+ '',
989
+ '',
990
+ '',
991
+ '',
992
+ '',
993
+ '',
994
+ '',
995
+ '',
996
+ 'Time [seconds]',
997
+ 'latitude [degrees]',
998
+ 'longitude [degrees]',
999
+ ]
1000
+
1001
+
1002
+ self.cols = [b if a == '' else a
1003
+ for a,b in zip(self.cols, self.defaultcolumnnames)]
1004
+ self.columns = dict(list(zip(self.defaultcolumnnames, self.cols)))
1005
+
1006
+ # calculations
1007
+ velo = self.df[self.columns[' Stroke500mPace (sec/500m)']]
1008
+ pace = 500./velo
1009
+ self.df[self.columns[' Stroke500mPace (sec/500m)']] = pace
1010
+
1011
+ # get date from header
1012
+ dateline = get_file_line(2,csvfile)
1013
+ row_datetime = parser.parse(dateline, fuzzy=True,yearfirst=True,dayfirst=False)
1014
+
1015
+ timestamp = arrow.get(row_datetime).timestamp()
1016
+
1017
+ time = self.df[self.columns['TimeStamp (sec)']]
1018
+ time += timestamp
1019
+ self.df[self.columns['TimeStamp (sec)']] = time
1020
+
1021
+ self.to_standard()
1022
+
1023
+ class RitmoTimeParser(CSVParser):
1024
+ def __init__(self, *args, **kwargs):
1025
+ if args:
1026
+ csvfile = args[0]
1027
+ else: # pragma: no cover
1028
+ csvfile = kwargs['csvfile']
1029
+
1030
+ skiprows = ritmo_variable_header(csvfile)
1031
+ kwargs['skiprows'] = skiprows
1032
+
1033
+ separator = get_separator(skiprows+2, csvfile)
1034
+ kwargs['sep'] = separator
1035
+
1036
+ super(RitmoTimeParser, self).__init__(*args, **kwargs)
1037
+ # crude EU format detector
1038
+ try:
1039
+ ll = self.df['Longitude (deg)']*10.0
1040
+ except TypeError: # pragma: no cover
1041
+ convertlistbase = [
1042
+ 'Total Time (sec)',
1043
+ 'Rate (spm)',
1044
+ 'Distance (m)',
1045
+ 'Speed (m/s)',
1046
+ 'Latitude (deg)',
1047
+ 'Longitude (deg)',
1048
+ ]
1049
+ converters = make_converter(convertlistbase,self.df)
1050
+
1051
+ kwargs['converters'] = converters
1052
+
1053
+ super(RitmoTimeParser, self).__init__(*args, **kwargs)
1054
+
1055
+ self.cols = [
1056
+ '',
1057
+ 'Distance (m)',
1058
+ 'Rate (spm)',
1059
+ 'Heart Rate (bpm)',
1060
+ 'Split (/500m)',
1061
+ '',
1062
+ '',
1063
+ '',
1064
+ '',
1065
+ '',
1066
+ '',
1067
+ '',
1068
+ '',
1069
+ 'Piece#',
1070
+ 'Total Time (sec)',
1071
+ 'Latitude (deg)',
1072
+ 'Longitude (deg)',
1073
+ ]
1074
+
1075
+ self.cols = [b if a == '' else a
1076
+ for a, b in zip(self.cols, self.defaultcolumnnames)]
1077
+
1078
+ self.columns = dict(list(zip(self.defaultcolumnnames, self.cols)))
1079
+
1080
+ # calculations / speed
1081
+ velo = self.df['Speed (m/s)']
1082
+ pace = 500. / velo
1083
+ self.df[self.columns[' Stroke500mPace (sec/500m)']] = pace
1084
+
1085
+ # add rest from state column
1086
+ self.df[' WorkoutState'] = self.df['State'].apply(lambda x: 3 if x.lower()=='rest' else 4)
1087
+
1088
+ # try date from first line
1089
+ firstline = get_file_line(1, csvfile)
1090
+ try:
1091
+ startdatetime = parser.parse(firstline,fuzzy=True)
1092
+ except ValueError: # pragma: no cover
1093
+ startdatetime = datetime.datetime.utcnow()
1094
+
1095
+ if startdatetime.tzinfo is None:
1096
+ try:
1097
+ latavg = self.df[self.columns[' latitude']].mean()
1098
+ lonavg = self.df[self.columns[' longitude']].mean()
1099
+ tf = TimezoneFinder()
1100
+ timezone_str = tf.timezone_at(lng=lonavg, lat=latavg)
1101
+ if timezone_str is None: # pragma: no cover
1102
+ timezone_str = tf.closest_timezone_at(lng=lonavg,lat=latavg)
1103
+
1104
+ startdatetime = pytz.timezone(timezone_str).localize(startdatetime)
1105
+ except KeyError: # pragma: no cover
1106
+ startdatetime = pytz.timezone('UTC').localize(startdatetime)
1107
+ timezonestr = 'UTC'
1108
+
1109
+ elapsed = self.df[self.columns[' ElapsedTime (sec)']]
1110
+ starttimeunix = arrow.get(startdatetime).timestamp()
1111
+ #tts = startdatetime + elapsed.apply(lambda x: datetime.timedelta(seconds=x))
1112
+ #unixtimes=tts.apply(lambda x: time.mktime(x.utctimetuple()))
1113
+ #unixtimes = tts.apply(lambda x: arrow.get(
1114
+ # x).timestamp() + arrow.get(x).microsecond / 1.e6)
1115
+ unixtimes = starttimeunix+elapsed
1116
+ self.df[self.columns['TimeStamp (sec)']] = unixtimes
1117
+
1118
+ self.to_standard()
1119
+
1120
+ class QuiskeParser(CSVParser):
1121
+ def __init__(self, *args, **kwargs):
1122
+ kwargs['skiprows'] = 1
1123
+ kwargs['sep'] = ';'
1124
+ if args:
1125
+ csvfile = args[0]
1126
+ else: # pragma: no cover
1127
+ csvfile = kwargs['csvfile']
1128
+
1129
+ firstline = get_file_line(1, csvfile)
1130
+ if ';' not in firstline:
1131
+ kwargs.pop('sep')
1132
+
1133
+ super(QuiskeParser, self).__init__(*args, **kwargs)
1134
+
1135
+ self.cols = [
1136
+ 'timestamp(s)',
1137
+ 'distance(m)',
1138
+ 'SPM (strokes per minute)',
1139
+ '',
1140
+ ' Stroke500mPace (sec/500m)',
1141
+ '',
1142
+ '',
1143
+ '',
1144
+ '',
1145
+ '',
1146
+ '',
1147
+ '',
1148
+ '',
1149
+ '',
1150
+ '',
1151
+ 'latitude',
1152
+ 'longitude',
1153
+ 'Catch',
1154
+ 'Finish',
1155
+ ]
1156
+
1157
+ self.defaultcolumnnames += [
1158
+ 'catch',
1159
+ 'finish',
1160
+ ]
1161
+
1162
+ self.cols = [b if a == '' else a
1163
+ for a, b in zip(self.cols, self.defaultcolumnnames)]
1164
+
1165
+ self.columns = dict(list(zip(self.defaultcolumnnames, self.cols)))
1166
+
1167
+ velo = self.df['speed (m/s)']
1168
+ pace = 500./velo
1169
+ pace = pace.replace(np.nan, 300)
1170
+ pace = pace.replace(np.inf, 300)
1171
+ self.df[self.columns[' Stroke500mPace (sec/500m)']] = pace
1172
+ unixtimes = self.df[self.columns['TimeStamp (sec)']]
1173
+ self.df[self.columns[' ElapsedTime (sec)']] = unixtimes - unixtimes.iloc[0]
1174
+ self.df[self.columns['catch']] = 0
1175
+ self.df[self.columns['finish']] = self.df['stroke angle (deg)']
1176
+
1177
+ self.to_standard()
1178
+
1179
+ class BoatCoachOTWParser(CSVParser):
1180
+
1181
+ def __init__(self, *args, **kwargs):
1182
+ if args:
1183
+ csvfile = args[0]
1184
+ else: # pragma: no cover
1185
+ csvfile = kwargs['csvfile']
1186
+
1187
+ skiprows = bc_variable_header(csvfile)
1188
+ kwargs['skiprows'] = skiprows
1189
+
1190
+ separator = get_separator(3, csvfile)
1191
+ kwargs['sep'] = separator
1192
+
1193
+ super(BoatCoachOTWParser, self).__init__(*args, **kwargs)
1194
+
1195
+ # 500m or km based
1196
+ try:
1197
+ pace = self.df['Last 10 Stroke Speed(/500m)']
1198
+ except KeyError: # pragma: no cover
1199
+ pace1 = self.df['Last 10 Stroke Speed(/km)']
1200
+ self.df['Last 10 Stroke Speed(/500m)'] = pace1.values
1201
+
1202
+
1203
+ # crude EU format detector
1204
+ try:
1205
+ ll = self.df['Longitude']*10.0
1206
+ except TypeError: # pragma: no cover
1207
+ convertlistbase = [
1208
+ 'TOTAL Distance Since Start BoatCoach(m)',
1209
+ 'Stroke Rate',
1210
+ 'Heart Rate',
1211
+ 'Latitude',
1212
+ 'Longitude',
1213
+ ]
1214
+ converters = make_converter(convertlistbase,self.df)
1215
+
1216
+ kwargs['converters'] = converters
1217
+
1218
+ super(BoatCoachOTWParser, self).__init__(*args, **kwargs)
1219
+
1220
+ self.cols = [
1221
+ 'DateTime',
1222
+ 'TOTAL Distance Since Start BoatCoach(m)',
1223
+ 'Stroke Rate',
1224
+ 'Heart Rate',
1225
+ 'Last 10 Stroke Speed(/500m)',
1226
+ '',
1227
+ '',
1228
+ '',
1229
+ '',
1230
+ '',
1231
+ '',
1232
+ '',
1233
+ '',
1234
+ 'Piece Number',
1235
+ 'Elapsed Time',
1236
+ 'Latitude',
1237
+ 'Longitude',
1238
+ ]
1239
+
1240
+ self.cols = [b if a == '' else a
1241
+ for a, b in zip(self.cols, self.defaultcolumnnames)]
1242
+
1243
+ self.columns = dict(list(zip(self.defaultcolumnnames, self.cols)))
1244
+
1245
+ try:
1246
+ row_datetime = self.df[self.columns['TimeStamp (sec)']]
1247
+ row_date = parser.parse(row_datetime[0], fuzzy=True,yearfirst=True,dayfirst=False)
1248
+ row_datetime = row_datetime.apply(
1249
+ lambda x: parser.parse(x, fuzzy=True,yearfirst=True,dayfirst=False))
1250
+ unixtimes = row_datetime.apply(lambda x: arrow.get(
1251
+ x).timestamp() + arrow.get(x).microsecond / 1.e6)
1252
+ except KeyError: # pragma: no cover
1253
+ row_date2 = arrow.get(row_date).timestamp()
1254
+ timecolumn = self.df[self.columns[' ElapsedTime (sec)']]
1255
+ timesecs = timecolumn.apply(timestrtosecs)
1256
+ timesecs = make_cumvalues(timesecs)[0]
1257
+ unixtimes = row_date2 + timesecs
1258
+
1259
+ self.df[self.columns['TimeStamp (sec)']] = unixtimes
1260
+ self.columns[' ElapsedTime (sec)'] = ' ElapsedTime (sec)'
1261
+
1262
+ self.df[self.columns[' ElapsedTime (sec)']] = unixtimes - unixtimes[0]
1263
+
1264
+ try: # pragma: no cover
1265
+ d = self.df['Last 10 Stroke Speed(/km)']
1266
+ multiplicator = 0.5
1267
+ except:
1268
+ multiplicator = 1
1269
+
1270
+
1271
+ pace = self.df[self.columns[' Stroke500mPace (sec/500m)']].apply(
1272
+ timestrtosecs2
1273
+ )
1274
+
1275
+ pace *= multiplicator
1276
+
1277
+
1278
+ self.df[self.columns[' Stroke500mPace (sec/500m)']] = pace
1279
+
1280
+
1281
+ self.to_standard()
1282
+
1283
+ class CoxMateParser(CSVParser):
1284
+
1285
+ def __init__(self, *args, **kwargs):
1286
+ super(CoxMateParser, self).__init__(*args, **kwargs)
1287
+ # remove "00 waiting to row"
1288
+
1289
+ self.cols = [
1290
+ '',
1291
+ 'Distance',
1292
+ 'Rating',
1293
+ 'Heart Rate',
1294
+ '',
1295
+ '',
1296
+ '',
1297
+ 'Cover',
1298
+ '',
1299
+ '',
1300
+ '',
1301
+ '',
1302
+ '',
1303
+ '',
1304
+ 'Time',
1305
+ '',
1306
+ '',
1307
+ ]
1308
+
1309
+ self.cols = [b if a == '' else a
1310
+ for a, b in zip(self.cols, self.defaultcolumnnames)]
1311
+
1312
+ self.columns = dict(list(zip(self.defaultcolumnnames, self.cols)))
1313
+
1314
+ # calculations / speed
1315
+ dd = self.df[self.columns[' Horizontal (meters)']].diff()
1316
+ dt = self.df[self.columns[' ElapsedTime (sec)']].diff()
1317
+ velo = dd / dt
1318
+ pace = 500. / velo
1319
+ self.df[self.columns[' Stroke500mPace (sec/500m)']] = pace
1320
+
1321
+ # calculations / time stamp
1322
+
1323
+ # convert to unix style time stamp
1324
+ now = datetime.datetime.utcnow()
1325
+ elapsed = self.df[self.columns[' ElapsedTime (sec)']]
1326
+ tts = now + elapsed.apply(lambda x: datetime.timedelta(seconds=x))
1327
+ #unixtimes=tts.apply(lambda x: time.mktime(x.utctimetuple()))
1328
+ unixtimes = tts.apply(lambda x: arrow.get(
1329
+ x).timestamp() + arrow.get(x).microsecond / 1.e6)
1330
+ self.df[self.columns['TimeStamp (sec)']] = unixtimes
1331
+
1332
+
1333
+ self.to_standard()
1334
+
1335
+
1336
+ class painsledDesktopParser(CSVParser):
1337
+
1338
+ def __init__(self, *args, **kwargs):
1339
+ super(painsledDesktopParser, self).__init__(*args, **kwargs)
1340
+ # remove "00 waiting to row"
1341
+ self.df = self.df[self.df[' stroke.endWorkoutState']
1342
+ != ' "00 waiting to row"']
1343
+
1344
+ self.cols = [
1345
+ ' stroke.driveStartMs',
1346
+ ' stroke.startWorkoutMeter',
1347
+ ' stroke.strokesPerMin',
1348
+ ' stroke.hrBpm',
1349
+ ' stroke.paceSecPer1k',
1350
+ ' stroke.watts',
1351
+ ' stroke.driveMeters',
1352
+ ' stroke.strokeMeters',
1353
+ ' stroke.driveMs',
1354
+ ' stroke.dragFactor',
1355
+ ' stroke.slideMs',
1356
+ '',
1357
+ '',
1358
+ ' stroke.intervalNumber',
1359
+ ' stroke.driveStartMs',
1360
+ ' latitude',
1361
+ ' longitude',
1362
+ ]
1363
+
1364
+ self.cols = [b if a == '' else a
1365
+ for a, b in zip(self.cols, self.defaultcolumnnames)]
1366
+
1367
+ self.columns = dict(list(zip(self.defaultcolumnnames, self.cols)))
1368
+
1369
+ # calculations
1370
+ pace = self.df[self.columns[' Stroke500mPace (sec/500m)']] / 2.
1371
+ pace = np.clip(pace, 0, 1e4)
1372
+ self.df[self.columns[' Stroke500mPace (sec/500m)']] = pace
1373
+ timestamps = self.df[self.columns['TimeStamp (sec)']]
1374
+ # convert to unix style time stamp
1375
+ tts = timestamps.apply(lambda x: iso8601.parse_date(x[2:-1]))
1376
+ #unixtimes=tts.apply(lambda x: time.mktime(x.utctimetuple()))
1377
+ unixtimes = tts.apply(lambda x: arrow.get(x).timestamp() + arrow.get(x).microsecond / 1.e6)
1378
+ self.df[self.columns['TimeStamp (sec)']] = unixtimes
1379
+ self.columns[' ElapsedTime (sec)'] = ' ElapsedTime (sec)'
1380
+ self.df[
1381
+ self.columns[' ElapsedTime (sec)']
1382
+ ] = unixtimes - unixtimes.iloc[0]
1383
+ self.to_standard()
1384
+
1385
+
1386
+ class BoatCoachParser(CSVParser):
1387
+
1388
+ def __init__(self, *args, **kwargs):
1389
+ kwargs['skiprows'] = 1
1390
+ kwargs['usecols'] = list(range(25))
1391
+
1392
+ if args:
1393
+ csvfile = args[0]
1394
+ else:
1395
+ csvfile = kwargs['csvfile']
1396
+
1397
+ ll = get_file_line(1,csvfile)
1398
+ if 'workoutType' in ll:
1399
+ kwargs['skiprows'] = 0
1400
+
1401
+ separator = get_separator(2, csvfile)
1402
+ kwargs['sep'] = separator
1403
+
1404
+ super(BoatCoachParser, self).__init__(*args, **kwargs)
1405
+
1406
+ # crude EU format detector
1407
+ try:
1408
+ p = self.df['stroke500MPace'] * 500.
1409
+ except TypeError:
1410
+ convertlistbase = ['workDistance',
1411
+ 'strokeRate',
1412
+ 'currentHeartRate',
1413
+ 'strokePower',
1414
+ 'strokeLength',
1415
+ 'strokeDriveTime',
1416
+ 'dragFactor',
1417
+ 'strokeAverageForce',
1418
+ 'strokePeakForce',
1419
+ 'intervalCount']
1420
+ converters = make_converter(convertlistbase,self.df)
1421
+
1422
+ kwargs['converters'] = converters
1423
+ super(BoatCoachParser, self).__init__(*args, **kwargs)
1424
+
1425
+
1426
+ self.cols = [
1427
+ 'DateTime',
1428
+ 'workDistance',
1429
+ 'strokeRate',
1430
+ 'currentHeartRate',
1431
+ 'stroke500MPace',
1432
+ 'strokePower',
1433
+ 'strokeLength',
1434
+ '',
1435
+ 'strokeDriveTime',
1436
+ 'dragFactor',
1437
+ ' StrokeRecoveryTime (ms)',
1438
+ 'strokeAverageForce',
1439
+ 'strokePeakForce',
1440
+ 'intervalCount',
1441
+ 'workTime',
1442
+ ' latitude',
1443
+ ' longitude',
1444
+ ]
1445
+
1446
+ self.cols = [b if a == '' else a
1447
+ for a, b in zip(self.cols, self.defaultcolumnnames)]
1448
+
1449
+ self.columns = dict(list(zip(self.defaultcolumnnames, self.cols)))
1450
+
1451
+ # get date from footer
1452
+ try:
1453
+ try:
1454
+ with open(csvfile, readmode) as fop:
1455
+ line = fop.readline()
1456
+ dated = re.split('Date:', line)[1][1:-1]
1457
+ except (IndexError,UnicodeDecodeError):
1458
+ with gzip.open(csvfile,readmode) as fop:
1459
+ line = fop.readline()
1460
+ dated = re.split('Date:', line)[1][1:-1]
1461
+ row_date = parser.parse(dated, fuzzy=True,yearfirst=True,dayfirst=False)
1462
+ except IOError:
1463
+ pass
1464
+
1465
+
1466
+ try:
1467
+ datetime = self.df[self.columns['TimeStamp (sec)']]
1468
+ row_date = parser.parse(datetime[0], fuzzy=True,yearfirst=True,dayfirst=False)
1469
+ row_datetime = datetime.apply(lambda x: parser.parse(x, fuzzy=True,yearfirst=True,dayfirst=False))
1470
+ unixtimes = row_datetime.apply(
1471
+ lambda x: arrow.get(x).timestamp() + arrow.get(x).microsecond / 1.e6
1472
+ )
1473
+ except KeyError:
1474
+ # calculations
1475
+ # row_date2=time.mktime(row_date.utctimetuple())
1476
+ row_date2 = arrow.get(row_date).timestamp()
1477
+ timecolumn = self.df[self.columns[' ElapsedTime (sec)']]
1478
+ timesecs = timecolumn.apply(timestrtosecs)
1479
+ timesecs = make_cumvalues(timesecs)[0]
1480
+ unixtimes = row_date2 + timesecs
1481
+
1482
+
1483
+ self.df[self.columns['TimeStamp (sec)']] = unixtimes
1484
+ self.columns[' ElapsedTime (sec)'] = ' ElapsedTime (sec)'
1485
+
1486
+ self.df[self.columns[' ElapsedTime (sec)']] = unixtimes - unixtimes[0]
1487
+
1488
+ pace = self.df[self.columns[' Stroke500mPace (sec/500m)']].apply(
1489
+ timestrtosecs)
1490
+ self.df[self.columns[' Stroke500mPace (sec/500m)']] = pace
1491
+
1492
+ self.df[self.columns[' DriveTime (ms)']] = 1.0e3 * \
1493
+ self.df[self.columns[' DriveTime (ms)']]
1494
+
1495
+ drivetime = self.df[self.columns[' DriveTime (ms)']]
1496
+ stroketime = 60. * 1000. / \
1497
+ (1.0 * self.df[self.columns[' Cadence (stokes/min)']])
1498
+ recoverytime = stroketime - drivetime
1499
+ recoverytime.replace(np.inf, np.nan)
1500
+ recoverytime.replace(-np.inf, np.nan)
1501
+ recoverytime = recoverytime.bfill()
1502
+
1503
+ self.df[self.columns[' StrokeRecoveryTime (ms)']] = recoverytime
1504
+
1505
+ # Reset Interval Count by StrokeCount
1506
+ res = make_cumvalues(self.df['strokeCount'])
1507
+ lapidx = res[1]
1508
+ strokecount = res[0]
1509
+ self.df['strokeCount'] = strokecount
1510
+ if lapidx.max() > 1:
1511
+ self.df[self.columns[' lapIdx']] = lapidx
1512
+
1513
+ # Recalculate power
1514
+ pace = self.df[self.columns[' Stroke500mPace (sec/500m)']]
1515
+
1516
+
1517
+ pace = np.clip(pace, 0, 1e4)
1518
+ pace = pace.replace(0, 300)
1519
+
1520
+ self.df[self.columns[' Stroke500mPace (sec/500m)']] = pace
1521
+ velocity = 500. / (1.0 * pace)
1522
+ power = 2.8 * velocity**3
1523
+
1524
+ dif = abs(power - self.df[self.columns[' Power (watts)']])
1525
+
1526
+ moving = self.df[self.columns[' Horizontal (meters)']].diff()
1527
+ moving = moving.apply(lambda x:abs(x))
1528
+
1529
+ power[dif < 5] = self.df[self.columns[' Power (watts)']][dif < 5]
1530
+
1531
+ power[dif > 1000] = self.df[self.columns[' Power (watts)']][dif > 1000]
1532
+
1533
+ power[moving <= 1] = 0
1534
+
1535
+ self.df[self.columns[' Power (watts)']] = power
1536
+
1537
+ # Calculate Stroke Rate during rest
1538
+ mask = (self.df['intervalType'] == 'Rest')
1539
+ for strokenr in self.df.loc[mask, 'strokeCount'].unique():
1540
+ mask2 = self.df['strokeCount'] == strokenr
1541
+ strokes = self.df.loc[mask2, 'strokeCount']
1542
+ timestamps = self.df.loc[mask2, self.columns['TimeStamp (sec)']]
1543
+ strokeduration = len(strokes) * timestamps.diff().mean()
1544
+ spm = 60. / strokeduration
1545
+ try:
1546
+ self.df[' Cadence (stokes/min)'] = self.df[' Cadence (stokes/min)'].astype(float)
1547
+ self.df.loc[mask2, self.columns[' Cadence (stokes/min)']] = spm
1548
+ except KeyError:
1549
+ pass
1550
+
1551
+
1552
+ # get stroke power
1553
+ data = []
1554
+ try:
1555
+
1556
+ with gzip.open(csvfile,readmode) as f:
1557
+ for line in f:
1558
+ s = line.split(',')
1559
+ data.append(','.join([str(x) for x in s[26:-1]]))
1560
+ except IOError:
1561
+ with open(csvfile,'r') as f:
1562
+ for line in f:
1563
+ s = line.split(',')
1564
+ data.append(','.join([str(x) for x in s[26:-1]]))
1565
+
1566
+ try:
1567
+ self.df['PowerCurve'] = data[2:]
1568
+ except ValueError:
1569
+ pass
1570
+
1571
+
1572
+ # dump empty lines at end
1573
+ endhorizontal = self.df.loc[self.df.index[-1],
1574
+ self.columns[' Horizontal (meters)']]
1575
+
1576
+ if endhorizontal == 0:
1577
+ self.df.drop(self.df.index[-1], inplace=True)
1578
+
1579
+ # check for decreasing dist (fixed distance workout)
1580
+ if self.df[self.columns[' Horizontal (meters)']].is_monotonic_decreasing:
1581
+ dist_decreasing = self.df[self.columns[' Horizontal (meters)']].copy()
1582
+ dist_increasing = dist_decreasing.max()-dist_decreasing
1583
+ self.df[self.columns[' Horizontal (meters)']] = dist_increasing
1584
+
1585
+ res = make_cumvalues(self.df[self.columns[' Horizontal (meters)']])
1586
+ self.df['cumdist'] = res[0]
1587
+ maxdist = self.df['cumdist'].max()
1588
+ mask = (self.df['cumdist'] == maxdist)
1589
+ while len(self.df.loc[mask]) > 2:
1590
+ mask = (self.df['cumdist'] == maxdist)
1591
+ self.df.drop(self.df.index[-1], inplace=True)
1592
+
1593
+ mask = (self.df['cumdist'] == maxdist)
1594
+ try:
1595
+ self.df.loc[
1596
+ mask,
1597
+ self.columns[' lapIdx']
1598
+ ] = self.df.loc[self.df.index[-3], self.columns[' lapIdx']]
1599
+ except IndexError: # pragma: no cover
1600
+ pass
1601
+
1602
+
1603
+ self.to_standard()
1604
+
1605
+ class KinoMapParser(CSVParser):
1606
+
1607
+ def __init__(self, *args, **kwargs):
1608
+ kwargs['skiprows'] = 0
1609
+ #kwargs['usecols'] = range(25)
1610
+
1611
+ if args:
1612
+ csvfile = args[0]
1613
+ else: # pragma: no cover
1614
+ csvfile = kwargs['csvfile']
1615
+
1616
+ super(KinoMapParser, self).__init__(*args, **kwargs)
1617
+
1618
+ self.cols = [
1619
+ 'Date',
1620
+ 'Distance',
1621
+ 'Cadence',
1622
+ 'Heart rate',
1623
+ 'Speed',
1624
+ 'Power',
1625
+ '',
1626
+ '',
1627
+ '',
1628
+ '',
1629
+ '',
1630
+ '',
1631
+ '',
1632
+ '',
1633
+ '',
1634
+ 'Latitude',
1635
+ 'Longitude',
1636
+ ]
1637
+
1638
+ self.cols = [b if a == '' else a
1639
+ for a, b in zip(self.cols, self.defaultcolumnnames)]
1640
+
1641
+ self.columns = dict(list(zip(self.defaultcolumnnames, self.cols)))
1642
+
1643
+ row_datetime = self.df[self.columns['TimeStamp (sec)']]
1644
+ row_datetime = row_datetime.apply(
1645
+ lambda x: parser.parse(x, fuzzy=True,yearfirst=True,dayfirst=False))
1646
+ #unixtimes=datetime.apply(lambda x: time.mktime(x.utctimetuple()))
1647
+ unixtimes = row_datetime.apply(lambda x: arrow.get(
1648
+ x).timestamp() + arrow.get(x).microsecond / 1.e6)
1649
+ self.df[self.columns['TimeStamp (sec)']] = unixtimes
1650
+ self.columns[' ElapsedTime (sec)'] = ' ElapsedTime (sec)'
1651
+
1652
+ self.df[self.columns[' ElapsedTime (sec)']] = unixtimes - unixtimes[0]
1653
+
1654
+ pace = self.df[self.columns[' Stroke500mPace (sec/500m)']].apply(
1655
+ lambda x: speedtopace(x, unit='kmh'))
1656
+ self.df[self.columns[' Stroke500mPace (sec/500m)']] = pace
1657
+
1658
+ res = make_cumvalues(self.df[self.columns[' Horizontal (meters)']])
1659
+ self.df['cumdist'] = res[0]
1660
+ maxdist = self.df['cumdist'].max()
1661
+ mask = (self.df['cumdist'] == maxdist)
1662
+ while len(self.df[mask]) > 2: # pragma: no cover
1663
+ mask = (self.df['cumdist'] == maxdist)
1664
+ self.df.drop(self.df.index[-1], inplace=True)
1665
+
1666
+ mask = (self.df['cumdist'] == maxdist)
1667
+
1668
+ self.to_standard()
1669
+
1670
+
1671
+ class BoatCoachAdvancedParser(CSVParser):
1672
+
1673
+ def __init__(self, *args, **kwargs): # pragma: no cover
1674
+ kwargs['skiprows'] = 1
1675
+ kwargs['usecols'] = list(range(25))
1676
+
1677
+ if args:
1678
+ csvfile = args[0]
1679
+ else:
1680
+ csvfile = kwargs['csvfile']
1681
+
1682
+ separator = get_separator(2, csvfile)
1683
+ kwargs['sep'] = separator
1684
+
1685
+
1686
+ super(BoatCoachAdvancedParser, self).__init__(*args, **kwargs)
1687
+ # crude EU format detector
1688
+ try:
1689
+ p = self.df['stroke500MPace'] * 500.
1690
+ except TypeError:
1691
+ convertlistbase = [
1692
+ 'workDistance',
1693
+ 'strokeRate',
1694
+ 'currentHeartRate',
1695
+ 'strokePower',
1696
+ 'strokeLength',
1697
+ 'strokeDriveTime',
1698
+ 'dragFactor',
1699
+ 'strokeAverageForce',
1700
+ 'strokePeakForce',
1701
+ 'intervalCount',
1702
+ ]
1703
+
1704
+ converters = make_converter(convertlistbase,self.df)
1705
+
1706
+ kwargs['converters'] = converters
1707
+ super(BoatCoachParser, self).__init__(*args, **kwargs)
1708
+
1709
+ self.cols = [
1710
+ 'DateTime',
1711
+ 'workDistance',
1712
+ 'strokeRate',
1713
+ 'currentHeartRate',
1714
+ 'stroke500MPace',
1715
+ 'strokePower',
1716
+ 'strokeLength',
1717
+ '',
1718
+ 'strokeDriveTime',
1719
+ 'dragFactor',
1720
+ ' StrokeRecoveryTime (ms)',
1721
+ 'strokeAverageForce',
1722
+ 'strokePeakForce',
1723
+ 'intervalCount',
1724
+ 'workTime',
1725
+ ' latitude',
1726
+ ' longitude',
1727
+ ]
1728
+
1729
+ self.cols = [b if a == '' else a
1730
+ for a, b in zip(self.cols, self.defaultcolumnnames)]
1731
+
1732
+ self.columns = dict(list(zip(self.defaultcolumnnames, self.cols)))
1733
+
1734
+ # get date from footer
1735
+ try:
1736
+ with open(csvfile, 'r') as fop:
1737
+ line = fop.readline()
1738
+ dated = re.split('Date:', line)[1][1:-1]
1739
+ except (IndexError,UnicodeDecodeError):
1740
+ with gzip.open(csvfile,readmode) as fop:
1741
+ line = fop.readline()
1742
+ dated = re.split('Date:', line)[1][1:-1]
1743
+
1744
+ row_date = parser.parse(dated, fuzzy=True,yearfirst=True,dayfirst=False)
1745
+
1746
+ try:
1747
+ row_datetime = self.df[self.columns['TimeStamp (sec)']]
1748
+ row_date = parser.parse(datetime[0], fuzzy=True,yearfirst=True,dayfirst=False)
1749
+ rowdatetime = row_datetime.apply(lambda x: parser.parse(x, fuzzy=True,yearfirst=True,dayfirst=False))
1750
+ unixtimes = row_datetime.apply(lambda x: arrow.get(
1751
+ x).timestamp() + arrow.get(x).microsecond / 1.e6)
1752
+ except KeyError:
1753
+ # calculations
1754
+ # row_date2=time.mktime(row_date.utctimetuple())
1755
+ row_date2 = arrow.get(row_date).timestamp()
1756
+ timecolumn = self.df[self.columns[' ElapsedTime (sec)']]
1757
+ timesecs = timecolumn.apply(timestrtosecs)
1758
+ timesecs = make_cumvalues(timesecs)[0]
1759
+ unixtimes = row_date2 + timesecs
1760
+
1761
+ self.df[self.columns['TimeStamp (sec)']] = unixtimes
1762
+ self.columns[' ElapsedTime (sec)'] = ' ElapsedTime (sec)'
1763
+
1764
+ self.df[self.columns[' ElapsedTime (sec)']] = unixtimes - unixtimes[0]
1765
+
1766
+ pace = self.df[self.columns[' Stroke500mPace (sec/500m)']].apply(
1767
+ timestrtosecs)
1768
+ self.df[self.columns[' Stroke500mPace (sec/500m)']] = pace
1769
+
1770
+ self.df[self.columns[' DriveTime (ms)']] = 1.0e3 * \
1771
+ self.df[self.columns[' DriveTime (ms)']]
1772
+
1773
+ # Calculate Recovery Time
1774
+ drivetime = self.df[self.columns[' DriveTime (ms)']]
1775
+ stroketime = 60. * 1000. / \
1776
+ (1.0 * self.df[self.columns[' Cadence (stokes/min)']])
1777
+ recoverytime = stroketime - drivetime
1778
+ recoverytime.replace(np.inf, np.nan)
1779
+ recoverytime.replace(-np.inf, np.nan)
1780
+ recoverytime = recoverytime.bfill()
1781
+ self.df[self.columns[' StrokeRecoveryTime (ms)']] = recoverytime
1782
+
1783
+ # Reset Interval Count by StrokeCount
1784
+ res = make_cumvalues(self.df['strokeCount'])
1785
+ lapidx = res[1]
1786
+ strokecount = res[0]
1787
+ self.df['strokeCount'] = strokecount
1788
+ if lapidx.max() > 1:
1789
+ self.df[self.columns[' lapIdx']] = lapidx
1790
+
1791
+ lapmax = self.df[self.columns[' lapIdx']].max()
1792
+
1793
+ # Recalculate power
1794
+ pace = self.df[self.columns[' Stroke500mPace (sec/500m)']]
1795
+ pace = np.clip(pace, 0, 1e4)
1796
+ pace = pace.replace(0, 300)
1797
+ self.df[self.columns[' Stroke500mPace (sec/500m)']] = pace
1798
+ velocity = 500. / pace
1799
+ power = 2.8 * velocity**3
1800
+ self.df[self.columns[' Power (watts)']] = power
1801
+
1802
+ # Calculate Stroke Rate during rest
1803
+ mask = (self.df['intervalType'] == 'Rest')
1804
+ for strokenr in self.df.loc[mask, 'strokeCount'].unique():
1805
+ mask2 = self.df['strokeCount'] == strokenr
1806
+ strokes = self.df.loc[mask2, 'strokeCount']
1807
+ timestamps = self.df.loc[mask2, self.columns['TimeStamp (sec)']]
1808
+ strokeduration = len(strokes) * timestamps.diff().mean()
1809
+ spm = 60. / strokeduration
1810
+ self.df.loc[mask2, self.columns[' Cadence (stokes/min)']] = spm
1811
+
1812
+ # dump empty lines at end
1813
+ endhorizontal = self.df.loc[self.df.index[-1],
1814
+ self.columns[' Horizontal (meters)']]
1815
+
1816
+ if endhorizontal == 0:
1817
+ self.df.drop(self.df.index[-1], inplace=True)
1818
+
1819
+ # check for decreasing dist (fixed distance workout)
1820
+ if self.df[self.columns[' Horizontal (meters)']].is_monotonic_decreasing:
1821
+ dist_decreasing = self.df[self.columns[' Horizontal (meters)']].copy()
1822
+ dist_increasing = dist_decreasing.max()-dist_decreasing
1823
+ self.df[self.columns[' Horizontal (meters)']] = dist_increasing
1824
+
1825
+ res = make_cumvalues(self.df[self.columns[' Horizontal (meters)']])
1826
+ self.df['cumdist'] = res[0]
1827
+ maxdist = self.df['cumdist'].max()
1828
+ mask = (self.df['cumdist'] == maxdist)
1829
+ while len(self.df[mask]) > 2:
1830
+ mask = (self.df['cumdist'] == maxdist)
1831
+ self.df.drop(self.df.index[-1], inplace=True)
1832
+
1833
+ mask = (self.df['cumdist'] == maxdist)
1834
+ self.df.loc[
1835
+ mask,
1836
+ self.columns[' lapIdx']
1837
+ ] = self.df.loc[self.df.index[-3], self.columns[' lapIdx']]
1838
+
1839
+ self.to_standard()
1840
+
1841
+
1842
+ class ErgDataParser(CSVParser):
1843
+
1844
+ def __init__(self, *args, **kwargs):
1845
+ super(ErgDataParser, self).__init__(*args, **kwargs)
1846
+
1847
+ self.row_date = kwargs.pop('row_date', datetime.datetime.utcnow())
1848
+ self.cols = [
1849
+ 'Time (seconds)',
1850
+ 'Distance (meters)',
1851
+ 'Stroke Rate',
1852
+ 'Heart Rate',
1853
+ 'Pace (seconds per 500m',
1854
+ ' Power (watts)',
1855
+ '',
1856
+ '',
1857
+ '',
1858
+ '',
1859
+ '',
1860
+ '',
1861
+ '',
1862
+ ' lapIdx',
1863
+ 'Time(sec)',
1864
+ ' latitude',
1865
+ ' longitude',
1866
+ ]
1867
+
1868
+ try:
1869
+ pace = self.df[self.cols[4]]
1870
+ except KeyError: # pragma: no cover
1871
+ self.cols[4] = 'Pace (seconds per 500m)'
1872
+ try:
1873
+ pace = self.df[self.cols[4]]
1874
+ except KeyError:
1875
+ self.cols[4] = 'Pace (seconds)'
1876
+
1877
+ self.cols = [b if a == '' else a
1878
+ for a, b in zip(self.cols, self.defaultcolumnnames)]
1879
+
1880
+ self.columns = dict(list(zip(self.defaultcolumnnames, self.cols)))
1881
+
1882
+ # calculations
1883
+ # get date from footer
1884
+ pace = self.df[self.columns[' Stroke500mPace (sec/500m)']]
1885
+ try:
1886
+ pace = np.clip(pace, 0, 1e4)
1887
+ pace = pace.replace(0, 300)
1888
+ except TypeError: # pragma: no cover
1889
+ pass
1890
+ self.df[self.columns[' Stroke500mPace (sec/500m)']] = pace
1891
+
1892
+ seconds = self.df[self.columns['TimeStamp (sec)']]
1893
+ firststrokeoffset = seconds.values[0]
1894
+ res = make_cumvalues(seconds)
1895
+ seconds2 = res[0] + seconds[0]
1896
+ lapidx = res[1]
1897
+ unixtime = seconds2 + totimestamp(self.row_date)
1898
+
1899
+ velocity = 500. / pace
1900
+ power = 2.8 * velocity**3
1901
+
1902
+ self.df[self.columns['TimeStamp (sec)']] = unixtime
1903
+ self.columns[' ElapsedTime (sec)'] = ' ElapsedTime (sec)'
1904
+ self.df[self.columns[' ElapsedTime (sec)']] = unixtime - unixtime[0]
1905
+ self.df[self.columns[' ElapsedTime (sec)']] += firststrokeoffset
1906
+
1907
+ self.df[self.columns[' lapIdx']] = lapidx
1908
+ self.df[self.columns[' Power (watts)']] = power
1909
+
1910
+ self.to_standard()
1911
+
1912
+ class HeroParser(CSVParser):
1913
+
1914
+ def __init__(self, *args, **kwargs):
1915
+ if args:
1916
+ csvfile = args[0]
1917
+ else: # pragma: no cover
1918
+ csvfile = kwargs.pop('csvfile', None)
1919
+
1920
+ headerdata = get_file_line(2,csvfile).split(',')
1921
+ rowdatetime = arrow.get(headerdata[0],'YYYY-MM-DD HH:mm:ss ZZ')
1922
+
1923
+ dragfactor = headerdata[6]
1924
+
1925
+ kwargs['skiprows'] = 3
1926
+ super(HeroParser, self).__init__(*args, **kwargs)
1927
+
1928
+ self.cols = [
1929
+ 'Time',
1930
+ 'Distance',
1931
+ 'Stroke Rate',
1932
+ 'HR',
1933
+ 'Split',
1934
+ 'Watts',
1935
+ 'Stroke Length',
1936
+ 'Distance per Stroke',
1937
+ 'Drive Time',
1938
+ 'Drag Factor',
1939
+ 'Recovery Time',
1940
+ 'Average Drive Force (N)',
1941
+ 'Peak Drive Force (N)',
1942
+ '',
1943
+ '',
1944
+ '',
1945
+ '',
1946
+ ]
1947
+
1948
+ starttimeunix = rowdatetime.timestamp()
1949
+ self.cols = [b if a == '' else a
1950
+ for a,b in zip(self.cols, self.defaultcolumnnames)]
1951
+ self.columns = dict(list(zip(self.defaultcolumnnames, self.cols)))
1952
+
1953
+ self.df[self.columns[' DriveTime (ms)']] *= 1000
1954
+ self.df[self.columns[' StrokeRecoveryTime (ms)']] *= 1000
1955
+ self.df[self.columns[' DriveLength (meters)']] /= 100.
1956
+ self.df[self.columns[' AverageDriveForce (lbs)']] /= lbstoN
1957
+ self.df[self.columns[' PeakDriveForce (lbs)']] /= lbstoN
1958
+ time = self.df[self.columns['TimeStamp (sec)']].apply(timestrtosecs2)
1959
+ self.df[self.columns['TimeStamp (sec)']] = time+starttimeunix
1960
+ self.df[' ElapsedTime (sec)'] = time
1961
+
1962
+ pace = self.df[self.columns[' Stroke500mPace (sec/500m)']].apply(timestrtosecs2)
1963
+
1964
+ self.df[self.columns[' Stroke500mPace (sec/500m)']] = pace
1965
+
1966
+ self.to_standard()
1967
+
1968
+ # pace column
1969
+
1970
+
1971
+ class SmartRowParser(CSVParser):
1972
+ def __init__(self, *args, **kwargs):
1973
+ if args:
1974
+ csvfile = args[0]
1975
+ else: # pragma: no cover
1976
+ csvfile = kwargs['csvfile']
1977
+
1978
+ separator = get_separator(5, csvfile)
1979
+ kwargs['sep'] = separator
1980
+
1981
+ super(SmartRowParser, self).__init__(*args, **kwargs)
1982
+
1983
+ self.cols = [
1984
+ 'Timestamp (UTC)',
1985
+ 'Distance (m)',
1986
+ 'Stroke rate (SPM)',
1987
+ 'Heart rate (bpm)',
1988
+ 'Actual split (s)',
1989
+ 'Actual power (W)',
1990
+ '', # 'DriveLength (meters)',
1991
+ '', #' StrokeDistance (meters)',
1992
+ '', #' DriveTime (ms)',
1993
+ '', #' DragFactor',
1994
+ '', #' StrokeRecoveryTime (ms)',
1995
+ '', #' AverageDriveForce (lbs)',
1996
+ '', #' PeakDriveForce (lbs)',
1997
+ '', #' lapIdx',
1998
+ 'Second (#)',
1999
+ '', #' latitude',
2000
+ '', #' longitude',
2001
+ ]
2002
+
2003
+ self.cols = [b if a == '' else a
2004
+ for a,b in zip(self.cols, self.defaultcolumnnames)]
2005
+ self.columns = dict(list(zip(self.defaultcolumnnames, self.cols)))
2006
+
2007
+ for c in self.cols:
2008
+ try:
2009
+ self.df[c] = pd.to_numeric(self.df[c],errors='coerce')
2010
+ self.df[c] = self.df[c].bfill()
2011
+ except KeyError:
2012
+ pass
2013
+
2014
+ startdatetime = datetime.datetime.utcnow()
2015
+
2016
+ elapsed = self.df[self.columns['TimeStamp (sec)']]
2017
+ elapsed = pd.to_numeric(elapsed,errors='coerce')
2018
+ starttimeunix = arrow.get(startdatetime).timestamp()
2019
+
2020
+ unixtimes = starttimeunix+elapsed
2021
+ unixtimes = unixtimes.bfill()
2022
+
2023
+ self.df[self.columns['TimeStamp (sec)']] = unixtimes
2024
+ self.df[self.columns[' ElapsedTime (sec)']] = unixtimes-starttimeunix
2025
+
2026
+
2027
+ self.to_standard()
2028
+
2029
+
2030
+ class speedcoachParser(CSVParser):
2031
+
2032
+ def __init__(self, *args, **kwargs):
2033
+ super(speedcoachParser, self).__init__(*args, **kwargs)
2034
+
2035
+ self.row_date = kwargs.pop('row_date', datetime.datetime.utcnow())
2036
+ self.cols = [
2037
+ 'Time(sec)',
2038
+ 'Distance(m)',
2039
+ 'Rate',
2040
+ 'HR',
2041
+ 'Split(sec)',
2042
+ ' Power (watts)',
2043
+ '',
2044
+ '',
2045
+ '',
2046
+ '',
2047
+ '',
2048
+ '',
2049
+ '',
2050
+ ' lapIdx',
2051
+ 'Time(sec)',
2052
+ ' latitude',
2053
+ ' longitude',
2054
+ ]
2055
+
2056
+ self.cols = [b if a == '' else a
2057
+ for a, b in zip(self.cols, self.defaultcolumnnames)]
2058
+
2059
+ self.columns = dict(list(zip(self.defaultcolumnnames, self.cols)))
2060
+
2061
+ # calculations
2062
+ # get date from footer
2063
+
2064
+ pace = self.df[self.columns[' Stroke500mPace (sec/500m)']]
2065
+ pace = np.clip(pace, 0, 1e4)
2066
+ self.df[self.columns[' Stroke500mPace (sec/500m)']] = pace
2067
+
2068
+ seconds = self.df[self.columns['TimeStamp (sec)']]
2069
+ unixtimes = seconds + totimestamp(self.row_date)
2070
+
2071
+ self.df[self.columns['TimeStamp (sec)']] = unixtimes
2072
+ self.columns[' ElapsedTime (sec)'] = ' ElapsedTime (sec)'
2073
+ self.df[self.columns[' ElapsedTime (sec)']] = unixtimes - unixtimes[0]
2074
+
2075
+ self.to_standard()
2076
+
2077
+ class ErgStickParser(CSVParser):
2078
+
2079
+ def __init__(self, *args, **kwargs):
2080
+ super(ErgStickParser, self).__init__(*args, **kwargs)
2081
+
2082
+ self.row_date = kwargs.pop('row_date', datetime.datetime.utcnow())
2083
+ self.cols = [
2084
+ 'Total elapsed time (s)',
2085
+ 'Total distance (m)',
2086
+ 'Stroke rate (/min)',
2087
+ 'Current heart rate (bpm)',
2088
+ 'Current pace (/500m)',
2089
+ 'Split average power (W)',
2090
+ 'Drive length (m)',
2091
+ 'Stroke distance (m)',
2092
+ 'Drive time (s)',
2093
+ 'Drag factor',
2094
+ 'Stroke recovery time (s)',
2095
+ 'Ave. drive force (lbs)',
2096
+ 'Peak drive force (lbs)',
2097
+ ' lapIdx',
2098
+ 'Total elapsed time (s)',
2099
+ ' latitude',
2100
+ ' longitude',
2101
+ ]
2102
+
2103
+ self.cols = [b if a == '' else a
2104
+ for a, b in zip(self.cols, self.defaultcolumnnames)]
2105
+
2106
+ self.columns = dict(list(zip(self.defaultcolumnnames, self.cols)))
2107
+
2108
+ # calculations
2109
+ try:
2110
+ self.df[self.columns[' DriveTime (ms)']] *= 1000.
2111
+ self.df[self.columns[' StrokeRecoveryTime (ms)']] *= 1000.
2112
+ except KeyError:
2113
+ pass
2114
+
2115
+ try:
2116
+ pace = self.df[self.columns[' Stroke500mPace (sec/500m)']]
2117
+ pace = np.clip(pace, 1, 1e4)
2118
+ self.df[self.columns[' Stroke500mPace (sec/500m)']] = pace
2119
+ except TypeError:
2120
+ pace = self.df[self.columns[' Stroke500mPace (sec/500m)']]
2121
+ pace = pace.apply(lambda x:flexistrptime(x))
2122
+ pace = pace.apply(lambda x:60*x.minute+x.second+x.microsecond/1.e6)
2123
+ pace = np.clip(pace, 1, 1e4)
2124
+ self.df[self.columns[' Stroke500mPace (sec/500m)']] = pace
2125
+
2126
+ self.df[self.columns[' Stroke500mPace (sec/500m)']] = pace
2127
+
2128
+ # check distance
2129
+ try:
2130
+ distance = self.df[self.columns[' Horizontal (meters)']]
2131
+ except KeyError:
2132
+ self.columns[' Horizontal (meters)'] = 'Total distance'
2133
+ distance = self.df[self.columns[' Horizontal (meters)']]
2134
+ distance = distance.apply(lambda x:int(x[:-2]))
2135
+ self.df[self.columns[' Horizontal (meters)']] = distance
2136
+
2137
+ #velocity = 500. / pace
2138
+ #power = 2.8 * velocity**3
2139
+
2140
+ #self.df[' Power (watts)'] = power
2141
+
2142
+ try:
2143
+ seconds = self.df[self.columns['TimeStamp (sec)']]
2144
+ except:
2145
+ self.columns['TimeStamp (sec)'] = 'Total elapsed time'
2146
+ seconds = self.df[self.columns['TimeStamp (sec)']]
2147
+ seconds = seconds.apply(lambda x:flexistrptime(x))
2148
+ seconds = seconds.apply(lambda x:3600*x.hour+60*x.minute+x.second+x.microsecond/1.e6)
2149
+
2150
+ res = make_cumvalues(seconds)
2151
+ seconds2 = res[0] + seconds[0]
2152
+ lapidx = res[1]
2153
+ unixtimes = seconds2 + totimestamp(self.row_date)
2154
+ self.df[self.columns[' lapIdx']] = lapidx
2155
+ self.df[self.columns['TimeStamp (sec)']] = unixtimes
2156
+ self.columns[' ElapsedTime (sec)'] = ' ElapsedTime (sec)'
2157
+ self.df[self.columns[' ElapsedTime (sec)']] = unixtimes - unixtimes.iloc[0]
2158
+
2159
+ self.to_standard()
2160
+
2161
+ class RowPerfectParser(CSVParser):
2162
+
2163
+ def __init__(self, *args, **kwargs):
2164
+ super(RowPerfectParser, self).__init__(*args, **kwargs)
2165
+
2166
+ # get stroke curve
2167
+ try:
2168
+ data = self.df['curve_data'].str[1:-1].str.split(',',
2169
+ expand=True)
2170
+ data = data.apply(pd.to_numeric, errors = 'coerce')
2171
+
2172
+ for cols in data.columns.tolist()[1:]:
2173
+ data[data<0] = np.nan
2174
+
2175
+ s = []
2176
+ for row in data.values.tolist():
2177
+ s.append(str(row)[1:-1])
2178
+
2179
+ self.df['curve_data'] = s
2180
+ except AttributeError as e:
2181
+ pass
2182
+ # print traceback.format_exc()
2183
+
2184
+ for c in self.df.columns:
2185
+ if c != 'curve_data':
2186
+ self.df[c] = pd.to_numeric(self.df[c], errors='coerce')
2187
+
2188
+ self.df.sort_values(by=['workout_interval_id', 'stroke_number'],
2189
+ ascending=[True, True], inplace=True)
2190
+
2191
+
2192
+ self.row_date = kwargs.pop('row_date', datetime.datetime.utcnow())
2193
+ self.cols = [
2194
+ 'time',
2195
+ 'distance',
2196
+ 'stroke_rate',
2197
+ 'pulse',
2198
+ '',
2199
+ 'power',
2200
+ 'stroke_length',
2201
+ 'distance_per_stroke',
2202
+ 'drive_time',
2203
+ 'k',
2204
+ 'recover_time',
2205
+ '',
2206
+ 'peak_force',
2207
+ 'workout_interval_id',
2208
+ 'time',
2209
+ ' latitude',
2210
+ ' longitude',
2211
+ 'work_per_pulse'
2212
+ ]
2213
+
2214
+ self.defaultcolumnnames += [
2215
+ 'driveenergy'
2216
+ ]
2217
+
2218
+
2219
+ self.cols = [b if a == '' else a
2220
+ for a, b in zip(self.cols, self.defaultcolumnnames)]
2221
+
2222
+ self.columns = dict(list(zip(self.defaultcolumnnames, self.cols)))
2223
+
2224
+
2225
+ # calculations
2226
+ self.df[self.columns[' DriveTime (ms)']] *= 1000.
2227
+ self.df[self.columns[' StrokeRecoveryTime (ms)']] *= 1000.
2228
+ self.df[self.columns[' PeakDriveForce (lbs)']] /= lbstoN
2229
+ self.df[self.columns[' DriveLength (meters)']] /= 100.
2230
+
2231
+ wperstroke = self.df['energy_per_stroke']
2232
+ fav = wperstroke / self.df[self.columns[' DriveLength (meters)']]
2233
+ fav /= lbstoN
2234
+
2235
+ self.df[self.columns[' AverageDriveForce (lbs)']] = fav
2236
+
2237
+ power = self.df[self.columns[' Power (watts)']]
2238
+ v = (power / 2.8)**(1. / 3.)
2239
+ pace = 500. / v
2240
+
2241
+ self.df[' Stroke500mPace (sec/500m)'] = pace
2242
+
2243
+ seconds = self.df[self.columns['TimeStamp (sec)']]
2244
+ newseconds,lapidx = make_cumvalues_array(seconds)
2245
+ newstrokenr,lapidx = make_cumvalues_array(self.df['stroke_number'],doequal=True)
2246
+ seconds2 = pd.Series(newseconds)+newseconds[0]
2247
+ res = make_cumvalues(seconds)
2248
+ #seconds2 = res[0] + seconds[0]
2249
+ #lapidx = res[1]
2250
+ unixtime = seconds2 + totimestamp(self.row_date)
2251
+
2252
+
2253
+ self.df[self.columns[' lapIdx']] = lapidx
2254
+ self.df[self.columns['TimeStamp (sec)']] = unixtime
2255
+ self.columns[' ElapsedTime (sec)'] = ' ElapsedTime (sec)'
2256
+ self.df[self.columns[' ElapsedTime (sec)']] = unixtime - unixtime.iloc[0]
2257
+
2258
+ self.to_standard()
2259
+
2260
+ class MysteryParser(CSVParser):
2261
+
2262
+ def __init__(self, *args, **kwargs):
2263
+ if args:
2264
+ csvfile = args[0]
2265
+ else:
2266
+ csvfile = kwargs['csvfile']
2267
+
2268
+ separator = get_separator(1, csvfile)
2269
+ kwargs['sep'] = separator
2270
+
2271
+ super(MysteryParser, self).__init__(*args, **kwargs)
2272
+ self.df = self.df.drop(self.df.index[[0]])
2273
+ self.row_date = kwargs.pop('row_date', datetime.datetime.utcnow())
2274
+
2275
+ kwargs['engine'] = 'python'
2276
+
2277
+
2278
+
2279
+ kwargs['sep'] = None
2280
+
2281
+ self.row_date = kwargs.pop('row_date', datetime.datetime.utcnow())
2282
+ self.cols = [
2283
+ 'Practice Elapsed Time (s)',
2284
+ 'Distance (m)',
2285
+ 'Stroke Rate (SPM)',
2286
+ 'HR (bpm)',
2287
+ ' Stroke500mPace (sec/500m)',
2288
+ ' Power (watts)',
2289
+ ' DriveLength (meters)',
2290
+ ' StrokeDistance (meters)',
2291
+ ' DriveTime (ms)',
2292
+ ' DragFactor',
2293
+ ' StrokeRecoveryTime (ms)',
2294
+ ' AverageDriveForce (lbs)',
2295
+ ' PeakDriveForce (lbs)',
2296
+ ' lapIdx',
2297
+ ' ElapsedTime (sec)',
2298
+ 'Lat',
2299
+ 'Lon',
2300
+ ]
2301
+
2302
+ self.cols = [b if a == '' else a
2303
+ for a, b in zip(self.cols, self.defaultcolumnnames)]
2304
+ self.columns = dict(list(zip(self.defaultcolumnnames, self.cols)))
2305
+
2306
+ # calculations
2307
+ velo = pd.to_numeric(self.df['Speed (m/s)'], errors='coerce')
2308
+
2309
+ pace = 500. / velo
2310
+ pace = pace.replace(np.nan, 300)
2311
+ pace = pace.replace(np.inf, 300)
2312
+ self.df[self.columns[' Stroke500mPace (sec/500m)']] = pace
2313
+
2314
+ seconds = self.df[self.columns['TimeStamp (sec)']]
2315
+ res = make_cumvalues_array(np.array(seconds))
2316
+ seconds3 = res[0]
2317
+ lapidx = res[1]
2318
+
2319
+ # HR versions
2320
+ try:
2321
+ hr = self.df[self.columns[' HRCur (bpm)']]
2322
+ except KeyError:
2323
+ hr = self.df['HR (BPM)']
2324
+ self.df[self.columns[' HRCur (bpm)']] = hr
2325
+
2326
+
2327
+ spm = self.df[self.columns[' Cadence (stokes/min)']]
2328
+ try:
2329
+ strokelength = velo / (spm / 60.)
2330
+ except TypeError: # pragma: no cover
2331
+ strokelength = 0*velo
2332
+
2333
+ unixtimes = pd.Series(seconds3 + totimestamp(self.row_date))
2334
+
2335
+ self.df[self.columns[' lapIdx']] = lapidx
2336
+ self.df[self.columns['TimeStamp (sec)']] = unixtimes
2337
+ self.columns[' ElapsedTime (sec)'] = ' ElapsedTime (sec)'
2338
+ self.df[self.columns[' ElapsedTime (sec)']] = unixtimes - unixtimes.iloc[0]
2339
+ self.df[self.columns[' StrokeDistance (meters)']] = strokelength
2340
+
2341
+ self.to_standard()
2342
+
2343
+ class RowProParser(CSVParser):
2344
+
2345
+ def __init__(self, *args, **kwargs):
2346
+
2347
+ if args:
2348
+ csvfile = args[0]
2349
+ else:
2350
+ csvfile = kwargs['csvfile']
2351
+
2352
+ separator = get_separator(15, csvfile)
2353
+
2354
+ skipfooter = skip_variable_footer(csvfile)
2355
+ kwargs['skipfooter'] = skipfooter
2356
+ kwargs['engine'] = 'python'
2357
+ kwargs['skiprows'] = 14
2358
+ kwargs['usecols'] = None
2359
+ kwargs['sep'] = separator
2360
+
2361
+ super(RowProParser, self).__init__(*args, **kwargs)
2362
+ self.footer = get_rowpro_footer(csvfile)
2363
+
2364
+ # crude EU format detector
2365
+ try:
2366
+ p = self.df['Pace'] * 500.
2367
+ except TypeError: # pragma: no cover
2368
+ convertlistbase = [
2369
+ 'Time',
2370
+ 'Distance',
2371
+ 'AvgPace',
2372
+ 'Pace',
2373
+ 'AvgWatts',
2374
+ 'Watts',
2375
+ 'SPM',
2376
+ 'EndHR'
2377
+ ]
2378
+
2379
+ converters = make_converter(convertlistbase,self.df)
2380
+ kwargs['converters'] = converters
2381
+ super(RowProParser, self).__init__(*args, **kwargs)
2382
+ self.footer = get_rowpro_footer(csvfile, converters=converters)
2383
+
2384
+ # replace key values
2385
+ footerwork = self.footer[self.footer['Type'] <= 1]
2386
+ maxindex = self.df.index[-1]
2387
+ endvalue = self.df.loc[maxindex, 'Time']
2388
+ #self.df.loc[-1, 'Time'] = 0
2389
+ dt = self.df['Time'].diff()
2390
+ therowindex = self.df[dt < 0].index
2391
+
2392
+
2393
+
2394
+ dateline = get_file_line(11, csvfile)
2395
+ dated = dateline.split(',')[0]
2396
+ dated2 = dateline.split(';')[0]
2397
+ try:
2398
+ self.row_date = parser.parse(dated, fuzzy=True,yearfirst=True,dayfirst=False)
2399
+ except ValueError: # pragma: no cover
2400
+ self.row_date = parser.parse(dated2, fuzzy=True,yearfirst=True,dayfirst=False)
2401
+
2402
+ self.cols = [
2403
+ 'Time',
2404
+ 'Distance',
2405
+ 'SPM',
2406
+ 'HR',
2407
+ 'Pace',
2408
+ 'Watts',
2409
+ '',
2410
+ '',
2411
+ '',
2412
+ '',
2413
+ '',
2414
+ '',
2415
+ '',
2416
+ ' lapIdx',
2417
+ ' ElapsedTime (sec)',
2418
+ ' latitude',
2419
+ ' longitude',
2420
+ ]
2421
+
2422
+ self.cols = [b if a == '' else a
2423
+ for a, b in zip(self.cols, self.defaultcolumnnames)]
2424
+
2425
+ self.columns = dict(list(zip(self.defaultcolumnnames, self.cols)))
2426
+
2427
+ # calculations
2428
+ self.df[self.columns[' Stroke500mPace (sec/500m)']] *= 500.0
2429
+ seconds = self.df[self.columns['TimeStamp (sec)']] / 1000.
2430
+ res = make_cumvalues(seconds)
2431
+ seconds2 = res[0] + seconds[0]
2432
+ lapidx = res[1]
2433
+ seconds3 = seconds2.interpolate()
2434
+ seconds3[0] = seconds[0]
2435
+ unixtimes = seconds3 + arrow.get(self.row_date).timestamp()
2436
+
2437
+ # seconds3 = pd.to_timedelta(seconds3, unit='s')
2438
+
2439
+
2440
+ # try:
2441
+ # tts = self.row_date + seconds3
2442
+ # unixtimes = tts.apply(lambda x: arrow.get(
2443
+ # x).timestamp() + arrow.get(x).microsecond / 1.e6)
2444
+ # except ValueError:
2445
+ # seconds3 = seconds2.interpolate()
2446
+
2447
+
2448
+
2449
+ self.df[self.columns[' lapIdx']] = lapidx
2450
+ self.df[self.columns['TimeStamp (sec)']] = unixtimes
2451
+ self.columns[' ElapsedTime (sec)'] = ' ElapsedTime (sec)'
2452
+ self.df[self.columns[' ElapsedTime (sec)']] = unixtimes - unixtimes.iloc[0]
2453
+
2454
+ self.to_standard()
2455
+
2456
+ class NKLiNKLogbookParser(CSVParser):
2457
+ def __init__(self, *args, **kwargs):
2458
+ if args:
2459
+ csvfile = args[0]
2460
+ else: # pragma: no cover
2461
+ csvfile = kwargs['csvfile']
2462
+
2463
+ firmware = kwargs.get('firmware',None)
2464
+ oarlength = kwargs.get('oarlength',None)
2465
+ inboard = kwargs.get('inboard',None)
2466
+
2467
+ if firmware is not None:
2468
+ try: # pragma: no cover
2469
+ firmware = float(firmware)
2470
+ except ValueError: # pragma: no cover
2471
+ firmware = None
2472
+
2473
+ super(NKLiNKLogbookParser, self).__init__(*args, **kwargs)
2474
+
2475
+ self.cols = [
2476
+ 'timestamp',
2477
+ 'gpsTotalDistance',
2478
+ 'strokeRate',
2479
+ 'heartRate',
2480
+ 'gpsPace',
2481
+ 'power',
2482
+ '',
2483
+ 'gpsDistStroke',
2484
+ 'driveTime',
2485
+ '',
2486
+ '',
2487
+ 'handleForceAvg',
2488
+ 'maxHandleForce',
2489
+ 'sessionIntervalId',
2490
+ 'elapsedTime',
2491
+ 'latitude',
2492
+ 'longitude',
2493
+ 'gpsInstaSpeed',
2494
+ 'catchAngle',
2495
+ 'slip',
2496
+ 'finishAngle',
2497
+ 'wash',
2498
+ 'realWorkPerStroke',
2499
+ 'positionOfMaxForce',
2500
+ 'impellerInstaSpeed',
2501
+ 'impellerTotalDistance',
2502
+ ]
2503
+
2504
+ self.defaultcolumnnames += [
2505
+ 'GPS Speed',
2506
+ 'catch',
2507
+ 'slip',
2508
+ 'finish',
2509
+ 'wash',
2510
+ 'driveenergy',
2511
+ 'peakforceangle',
2512
+ # 'cum_dist',
2513
+ 'ImpellerSpeed',
2514
+ 'ImpellerDistance',
2515
+ ]
2516
+
2517
+ self.cols = [b if a == '' else a
2518
+ for a, b in zip(self.cols, self.defaultcolumnnames)]
2519
+
2520
+ self.columns = dict(list(zip(self.defaultcolumnnames, self.cols)))
2521
+
2522
+
2523
+ # do something with impeller stuff
2524
+ self.df['GPSSpeed'] = self.df['gpsInstaSpeed']
2525
+ self.df['GPSDistance'] = self.df['gpsTotalDistance']
2526
+
2527
+ # force is in Newtons
2528
+ try:
2529
+ self.df[self.columns[' PeakDriveForce (lbs)']] /= lbstoN
2530
+ self.df[self.columns[' AverageDriveForce (lbs)']] /= lbstoN
2531
+ except KeyError: # pragma: no cover # no oarlock data
2532
+ pass
2533
+
2534
+ # timestamp is in milliseconds
2535
+ self.df[self.columns['TimeStamp (sec)']] /= 1000.
2536
+ self.df[self.columns[' ElapsedTime (sec)']] /= 1000.
2537
+ self.df[self.columns[' Stroke500mPace (sec/500m)']] /= 1000.
2538
+
2539
+ try:
2540
+ self.df[' StrokeRecoveryTime (ms)'] = self.df['cycleTime']-self.df[self.columns[' DriveTime (ms)']]
2541
+ except KeyError: # pragma: no cover
2542
+ pass
2543
+
2544
+ corr_factor = 1.0
2545
+ if firmware is not None:
2546
+ if firmware < 2.18: # pragma: no cover
2547
+ # apply correction
2548
+ oarlength, inboard = get_empower_rigging(csvfile)
2549
+ if oarlength is not None and oarlength > 3.30:
2550
+ # sweep
2551
+ a = 0.15
2552
+ b = 0.275
2553
+ corr_factor = empower_bug_correction(oarlength,inboard,a,b)
2554
+ elif oarlength is not None and oarlength <= 3.3:
2555
+ # scull
2556
+ a = 0.06
2557
+ b = 0.225
2558
+ corr_factor = empower_bug_correction(oarlength,inboard,a,b)
2559
+
2560
+ try:
2561
+ self.df[self.columns[' Power (watts)']] *= corr_factor
2562
+ self.df[self.columns['driveenergy']] *= corr_factor
2563
+ except KeyError: # pragma: no cover
2564
+ pass
2565
+
2566
+ res = make_cumvalues(self.df[self.columns[' Horizontal (meters)']])
2567
+ self.df['cumdist'] = res[0]
2568
+ self.df['cum_dist'] = res[0]
2569
+
2570
+
2571
+ self.to_standard()
2572
+
2573
+ self.df = self.df.sort_values(by='TimeStamp (sec)',ascending=True)
2574
+
2575
+ def impellerconsistent(self, threshold = 0.3): # pragma: no cover
2576
+ impellerconsistent = True
2577
+ try:
2578
+ impspeed = self.df['ImpellerSpeed']
2579
+ except KeyError:
2580
+ return False, True, 0
2581
+
2582
+ nrvalues = len(impspeed)
2583
+
2584
+ impspeed.replace(to_replace=[np.nan, np.inf],inplace=True,value=0)
2585
+ nrvalid = impspeed.astype(bool).sum()
2586
+
2587
+ ratio = float(nrvalues-nrvalid)/float(nrvalues)
2588
+
2589
+ if ratio > threshold:
2590
+ impellerconsistent = False
2591
+
2592
+ return True, impellerconsistent, ratio
2593
+
2594
+
2595
+
2596
+ class SpeedCoach2Parser(CSVParser):
2597
+
2598
+ def __init__(self, *args, **kwargs):
2599
+
2600
+ if args:
2601
+ csvfile = args[0]
2602
+ else:
2603
+ csvfile = kwargs['csvfile']
2604
+
2605
+ skiprows, summaryline, blanklines, sessionline = skip_variable_header(csvfile)
2606
+
2607
+ firmware = get_empower_firmware(csvfile)
2608
+ corr_factor = 1.0
2609
+ if firmware is not None:
2610
+ if firmware < 2.18:
2611
+ # apply correction
2612
+ oarlength, inboard = get_empower_rigging(csvfile)
2613
+ if oarlength is not None and oarlength > 3.30: # pragma: no cover
2614
+ # sweep
2615
+ a = 0.15
2616
+ b = 0.275
2617
+ corr_factor = empower_bug_correction(oarlength,inboard,a,b)
2618
+ elif oarlength is not None and oarlength <= 3.3:
2619
+ # scull
2620
+ a = 0.06
2621
+ b = 0.225
2622
+ corr_factor = empower_bug_correction(oarlength,inboard,a,b)
2623
+
2624
+
2625
+
2626
+ unitrow = get_file_line(skiprows + 2, csvfile)
2627
+ self.velo_unit = 'ms'
2628
+ self.dist_unit = 'm'
2629
+ if 'KPH' in unitrow:
2630
+ self.velo_unit = 'kph'
2631
+ if 'MPH' in unitrow: # pragma: no cover
2632
+ self.velo_unit = 'mph'
2633
+
2634
+ if 'Kilometer' in unitrow:
2635
+ self.dist_unit = 'km'
2636
+
2637
+ if 'Newtons' in unitrow:
2638
+ self.force_unit = 'N'
2639
+ else:
2640
+ self.force_unit = 'lbs'
2641
+
2642
+ kwargs['skiprows'] = skiprows
2643
+ super(SpeedCoach2Parser, self).__init__(*args, **kwargs)
2644
+ self.df = self.df.drop(self.df.index[[0]])
2645
+
2646
+ for c in self.df.columns:
2647
+ if c not in ['Elapsed Time']:
2648
+ self.df[c] = pd.to_numeric(self.df[c], errors='coerce')
2649
+
2650
+ self.cols = [
2651
+ 'Elapsed Time',
2652
+ 'GPS Distance',
2653
+ 'Stroke Rate',
2654
+ 'Heart Rate',
2655
+ 'Split (GPS)',
2656
+ 'Power',
2657
+ '',
2658
+ '',
2659
+ '',
2660
+ '',
2661
+ '',
2662
+ 'Force Avg',
2663
+ 'Force Max',
2664
+ 'Interval',
2665
+ ' ElapsedTime (sec)',
2666
+ 'GPS Lat.',
2667
+ 'GPS Lon.',
2668
+ 'GPS Speed',
2669
+ 'Catch',
2670
+ 'Slip',
2671
+ 'Finish',
2672
+ 'Wash',
2673
+ 'Work',
2674
+ 'Max Force Angle',
2675
+ 'cum_dist',
2676
+ ]
2677
+
2678
+ self.defaultcolumnnames += [
2679
+ 'GPS Speed',
2680
+ 'catch',
2681
+ 'slip',
2682
+ 'finish',
2683
+ 'wash',
2684
+ 'driveenergy',
2685
+ 'peakforceangle',
2686
+ 'cum_dist',
2687
+ ]
2688
+
2689
+ self.cols = [b if a == '' else a
2690
+ for a, b in zip(self.cols, self.defaultcolumnnames)]
2691
+
2692
+ self.columns = dict(list(zip(self.defaultcolumnnames, self.cols)))
2693
+
2694
+ # correct Power, Work per Stroke
2695
+ try:
2696
+ self.df[self.columns[' Power (watts)']] *= corr_factor
2697
+ self.df[self.columns['driveenergy']] *= corr_factor
2698
+ except KeyError:
2699
+ pass
2700
+
2701
+ # set GPS speed apart for swapping
2702
+ try:
2703
+ self.df['GPSSpeed'] = self.df['GPS Speed']
2704
+ self.df['GPSDistance'] = self.df['GPS Distance']
2705
+ except KeyError:
2706
+ try:
2707
+ self.df['GPSSpeed'] = self.df['Speed (GPS)']
2708
+ self.df['GPSDistance'] = self.df['Distance (GPS)']
2709
+ self.columns['GPS Speed'] = 'Speed (GPS)'
2710
+ self.columns[' Horizontal (meters)'] = 'Distance (GPS)'
2711
+ except KeyError:
2712
+ pass
2713
+
2714
+ if self.velo_unit != 'ms':
2715
+ if self.velo_unit == 'kph':
2716
+ self.df['GPSSpeed'] = self.df['GPSSpeed'] / 3.6
2717
+ self.df['Speed (IMP)'] = self.df['Speed (IMP)'] / 3.6
2718
+ if self.velo_unit == 'mph':
2719
+ self.df['GPSSpeed'] = self.df['GPSSpeed'] * 0.44704
2720
+ self.df['Speed (IMP)'] = self.df['Speed (IMP)'] * 0.44704
2721
+
2722
+ # take Impeller split / speed if available and not zero
2723
+ try:
2724
+ impspeed = self.df['Speed (IMP)']
2725
+ self.columns['GPS Speed'] = 'Speed (IMP)'
2726
+ self.columns[' Horizontal (meters)'] = 'Distance (IMP)'
2727
+ self.df['ImpellerSpeed'] = impspeed
2728
+ self.df['ImpellerDistance'] = self.df['Distance (IMP)']
2729
+ except KeyError:
2730
+ try:
2731
+ impspeed = self.df['Imp Speed']
2732
+ self.columns['GPS Speed'] = 'Imp Speed'
2733
+ self.columns[' Horizontal (meters)'] = 'Imp Distance'
2734
+ self.df['ImpellerSpeed'] = impspeed
2735
+ self.df['ImpellerDistance'] = self.df['Imp Distance']
2736
+ except KeyError:
2737
+ impspeed = 0*self.df[self.columns['GPS Speed']]
2738
+
2739
+ if impspeed.std() != 0 and impspeed.mean() != 0:
2740
+ self.df[self.columns['GPS Speed']] = impspeed
2741
+ else:
2742
+ self.columns['GPS Speed'] = 'GPS Speed'
2743
+ self.columns[' Horizontal (meters)'] = 'GPS Distance'
2744
+
2745
+ #
2746
+
2747
+ try:
2748
+ dist2 = self.df[self.columns[' Horizontal (meters)']]
2749
+ except KeyError:
2750
+ try:
2751
+ dist2 = self.df['Distance (GPS)']
2752
+ self.columns[' Horizontal (meters)'] = 'Distance (GPS)'
2753
+ if 'GPS' in self.columns['GPS Speed']:
2754
+ self.columns['GPS Speed'] = 'Speed (GPS)'
2755
+ except KeyError: # pragma: no cover
2756
+ try:
2757
+ dist2 = self.df['Imp Distance']
2758
+ self.columns[' Horizontal (meters)'] = 'Distance (GPS)'
2759
+ self.columns[' Stroke500mPace (sec/500m)'] = 'Imp Split'
2760
+ self.columns[' Power (watts)'] = 'Work'
2761
+ self.columns['Work'] = 'Power'
2762
+ self.columns['GPS Speed'] = 'Imp Speed'
2763
+ except KeyError:
2764
+ dist2 = self.df['Distance (IMP)']
2765
+ self.columns[' Stroke500mPace (sec/500m)'] = 'Split (IMP)'
2766
+ self.columns[' Horizontal (meters)'] = 'Distance (GPS)'
2767
+ self.columns[' Power (watts)'] = 'Work'
2768
+ self.columns['Work'] = 'Power'
2769
+ self.columns['GPS Speed'] = 'Speed (IMP)'
2770
+
2771
+ try:
2772
+ if self.force_unit == 'N':
2773
+ self.df[self.columns[' PeakDriveForce (lbs)']] /= lbstoN
2774
+ self.df[self.columns[' AverageDriveForce (lbs)']] /= lbstoN
2775
+ except KeyError: # pragma: no cover
2776
+ pass
2777
+
2778
+ if self.dist_unit == 'km':
2779
+ #dist2 *= 1000
2780
+ self.df[self.columns[' Horizontal (meters)']] *= 1000.
2781
+ try:
2782
+ self.df['GPSDistance'] *= 1000.
2783
+ except KeyError: # pragma: no cover
2784
+ pass
2785
+ try:
2786
+ self.df['ImpellerDistance'] *= 1000.
2787
+ except KeyError: # pragma: no cover
2788
+ pass
2789
+
2790
+ cum_dist = make_cumvalues_array(dist2.ffill().values)[0]
2791
+ self.df[self.columns['cum_dist']] = cum_dist
2792
+ velo = self.df[self.columns['GPS Speed']]
2793
+ if self.velo_unit == 'kph':
2794
+ velo = velo / 3.6
2795
+ if self.velo_unit == 'mph': # pragma: no cover
2796
+ velo = velo * 0.44704
2797
+
2798
+ pace = 500. / velo
2799
+ pace = pace.replace(np.nan, 300)
2800
+ self.df[self.columns[' Stroke500mPace (sec/500m)']] = pace
2801
+
2802
+ # get date from header
2803
+ try:
2804
+ dateline = get_file_line(4, csvfile)
2805
+ dated = dateline.split(',')[1]
2806
+ # self.row_date = parser.parse(dated, fuzzy=True, dayfirst=False)
2807
+ self.row_date = parser.parse(dated,fuzzy=False,dayfirst=False)
2808
+ alt_date = parser.parse(dated,fuzzy=False,dayfirst=True)
2809
+ except ValueError:
2810
+ dateline = get_file_line(3, csvfile)
2811
+ dated = dateline.split(',')[1]
2812
+ try:
2813
+ # self.row_date = parser.parse(dated, fuzzy=True,dayfirst=False)
2814
+ self.row_date = parser.parse(dated, fuzzy=False,dayfirst=False)
2815
+ alt_date = parser.parse(dated,fuzzy=False,dayfirst=True)
2816
+ except ValueError:
2817
+ self.row_date = datetime.datetime.now()
2818
+ alt_date = self.row_date
2819
+
2820
+ if alt_date.month == datetime.datetime.now().month:
2821
+ if alt_date != self.row_date:
2822
+ self.row_date = alt_date
2823
+
2824
+
2825
+ if self.row_date.tzinfo is None or self.row_date.tzinfo.utcoffset(self.row_date) is None:
2826
+ try:
2827
+ latavg = self.df[self.columns[' latitude']].mean()
2828
+ lonavg = self.df[self.columns[' longitude']].mean()
2829
+ tf = TimezoneFinder()
2830
+ timezone_str = tf.timezone_at(lng=lonavg, lat=latavg)
2831
+ if timezone_str is None: # pragma: no cover
2832
+ timezone_str = tf.closest_timezone_at(lng=lonavg,
2833
+ lat=latavg)
2834
+ row_date = self.row_date
2835
+ row_date = pytz.timezone(timezone_str).localize(row_date)
2836
+ except KeyError:
2837
+ row_date = pytz.timezone('UTC').localize(self.row_date)
2838
+ self.row_date = row_date
2839
+
2840
+
2841
+ timestrings = self.df[self.columns['TimeStamp (sec)']]
2842
+ seconds = timestrings.apply(
2843
+ lambda x: timestrtosecs2(x, unknown=np.nan)
2844
+ )
2845
+ seconds = clean_nan(np.array(seconds))
2846
+ seconds = pd.Series(seconds).ffill().values
2847
+ res = make_cumvalues_array(np.array(seconds))
2848
+ seconds3 = res[0]
2849
+ lapidx = res[1]
2850
+
2851
+ unixtimes = seconds3 + totimestamp(self.row_date)
2852
+
2853
+ if not self.df.empty:
2854
+ self.df[self.columns[' lapIdx']] = lapidx
2855
+ self.df[self.columns['TimeStamp (sec)']] = unixtimes
2856
+ self.columns[' ElapsedTime (sec)'] = ' ElapsedTime (sec)'
2857
+ self.df[self.columns[' ElapsedTime (sec)']] = unixtimes - unixtimes[0]
2858
+
2859
+ self.to_standard()
2860
+
2861
+ # Read summary data
2862
+ skipfooter = 7 + len(self.df)
2863
+ if not blanklines:
2864
+ skipfooter = skipfooter - 3
2865
+ if summaryline:
2866
+ try:
2867
+ self.summarydata = pd.read_csv(csvfile,
2868
+ skiprows=summaryline,
2869
+ skipfooter=skipfooter,
2870
+ engine='python')
2871
+ self.summarydata.drop(0, inplace=True)
2872
+ except: # pragma: no cover
2873
+ self.summarydata = pd.DataFrame()
2874
+ else:
2875
+ self.summarydata = pd.DataFrame()
2876
+
2877
+ skipfooter = 11 + len(self.df)+len(self.summarydata)
2878
+ if not blanklines:
2879
+ skipfooter = skipfooter - 3
2880
+ if sessionline:
2881
+ try:
2882
+ self.sessiondata = pd.read_csv(csvfile,
2883
+ skiprows= sessionline,
2884
+ skipfooter=skipfooter,
2885
+ engine='python')
2886
+ self.sessiondata.drop(0,inplace=True)
2887
+ except: # pragma: no cover
2888
+ self.sessiondata = pd.DataFrame()
2889
+ else:
2890
+ self.sessiondata = pd.DataFrame()
2891
+
2892
+ def impellerconsistent(self, threshold = 0.3):
2893
+ impellerconsistent = True
2894
+ try:
2895
+ impspeed = self.df['ImpellerSpeed']
2896
+ except KeyError: # pragma: no cover
2897
+ return False, True, 0
2898
+
2899
+ nrvalues = len(impspeed)
2900
+
2901
+ impspeed.replace(to_replace=[np.nan, np.inf], inplace=True,value=0)
2902
+ nrvalid = impspeed.astype(bool).sum()
2903
+
2904
+ ratio = float(nrvalues-nrvalid)/float(nrvalues)
2905
+
2906
+ if ratio > threshold:
2907
+ impellerconsistent = False
2908
+
2909
+ return True, impellerconsistent, ratio
2910
+
2911
+ def allstats(self, separator='|'):
2912
+ stri = self.summary(separator=separator) + \
2913
+ self.intervalstats(separator=separator)
2914
+ return stri
2915
+
2916
+ def sessionsummary(self, separator= '|'):
2917
+ if self.sessiondata.empty:
2918
+ return None
2919
+
2920
+ stri1 = "Workout Summary - " + self.csvfile + "\n"
2921
+ stri1 += "--{sep}Total{sep}-Total-{sep}--Avg--{sep}-Avg-{sep}-Avg--{sep}-Avg-{sep}-Max-{sep}-Avg\n".format(
2922
+ sep=separator)
2923
+ stri1 += "--{sep}Dist-{sep}-Time--{sep}-Pace--{sep}-Pwr-{sep}-SPM--{sep}-HR--{sep}-HR--{sep}-DPS\n".format(
2924
+ sep=separator)
2925
+
2926
+ try:
2927
+ dist = self.sessiondata['Total Distance (GPS)'].astype(float).mean()
2928
+ except (KeyError,ValueError):
2929
+ try:
2930
+ dist = self.sessiondata['Total Distance'].astype(float).mean()
2931
+ except (ValueError,KeyError):
2932
+ dist = 0.0
2933
+
2934
+
2935
+ timestring = self.sessiondata['Total Elapsed Time'].values[0]
2936
+ timestring = flexistrftime(flexistrptime(timestring))
2937
+
2938
+ try:
2939
+ pacestring = self.sessiondata['Avg Split (GPS)'].values[0]
2940
+ except KeyError:
2941
+ pacestring = self.sessiondata['Avg Split'].values[0]
2942
+
2943
+ pacestring = flexistrftime(flexistrptime(pacestring))
2944
+ try:
2945
+ pwr = self.sessiondata['Avg Power'].astype(float).mean()
2946
+ except (KeyError,ValueError):
2947
+ pwr = 0.0
2948
+
2949
+
2950
+ try:
2951
+ spm = self.sessiondata['Avg Stroke Rate'].astype(float).mean()
2952
+ except (ValueError,KeyError):
2953
+ spm = 0
2954
+
2955
+ try:
2956
+ avghr = self.sessiondata['Avg Heart Rate'].astype(float).mean()
2957
+ except (ValueError,KeyError):
2958
+ avghr = 0
2959
+
2960
+ try:
2961
+ avgdps = self.sessiondata['Distance/Stroke (GPS)'].astype(float).mean()
2962
+ except KeyError:
2963
+ avgdps = 0
2964
+
2965
+ try:
2966
+ maxhr = self.df[self.columns[' HRCur (bpm)']].max()
2967
+ except KeyError: # pragma: no cover
2968
+ maxhr = 0
2969
+
2970
+ stri1 += "--{sep}{dist:0>5.0f}{sep}".format(
2971
+ sep=separator,
2972
+ dist=dist,
2973
+ )
2974
+
2975
+ stri1 += timestring + separator + pacestring
2976
+
2977
+ stri1 += "{sep}{avgpower:0>5.1f}".format(
2978
+ sep=separator,
2979
+ avgpower=pwr,
2980
+ )
2981
+
2982
+ stri1 += "{sep} {avgsr:2.1f} {sep}{avghr:0>5.1f}{sep}".format(
2983
+ avgsr=spm,
2984
+ sep=separator,
2985
+ avghr=avghr
2986
+ )
2987
+
2988
+ stri1 += "{maxhr:0>5.1f}{sep}{avgdps:0>4.1f}\n".format(
2989
+ sep=separator,
2990
+ maxhr=maxhr,
2991
+ avgdps=avgdps
2992
+ )
2993
+
2994
+ return stri1
2995
+
2996
+
2997
+ def summary(self, separator='|'):
2998
+ if self.sessionsummary() is not None:
2999
+ return self.sessionsummary()
3000
+
3001
+ stri1 = "Workout Summary - " + self.csvfile + "\n"
3002
+ stri1 += "--{sep}Total{sep}-Total-{sep}--Avg--{sep}-Avg-{sep}Avg-{sep}-Avg-{sep}-Max-{sep}-Avg\n".format(
3003
+ sep=separator)
3004
+ stri1 += "--{sep}Dist-{sep}-Time--{sep}-Pace--{sep}-Pwr-{sep}SPM-{sep}-HR--{sep}-HR--{sep}-DPS\n".format(
3005
+ sep=separator)
3006
+
3007
+ d = self.df[self.columns['cum_dist']]
3008
+ dist = d.max() - d.min()
3009
+ t = self.df[self.columns['TimeStamp (sec)']]
3010
+ ttime = t.max() - t.min()
3011
+ pace = self.df[self.columns[' Stroke500mPace (sec/500m)']].mean()
3012
+ try:
3013
+ pwr = self.df[self.columns[' Power (watts)']].mean()
3014
+ except KeyError:
3015
+ pwr = 0
3016
+
3017
+ spm = self.df[self.columns[' Cadence (stokes/min)']].mean()
3018
+ try:
3019
+ avghr = self.df[self.columns[' HRCur (bpm)']].mean()
3020
+ maxhr = self.df[self.columns[' HRCur (bpm)']].max()
3021
+ except KeyError:
3022
+ avghr = 0
3023
+ maxhr = 0
3024
+
3025
+ pacestring = format_pace(pace)
3026
+ timestring = format_time(ttime)
3027
+ avgdps = self.df['Distance/Stroke (GPS)'].mean()
3028
+
3029
+ stri1 += "--{sep}{dist:0>5.0f}{sep}".format(
3030
+ sep=separator,
3031
+ dist=dist,
3032
+ )
3033
+
3034
+ stri1 += timestring + separator + pacestring
3035
+
3036
+ stri1 += "{sep}{avgpower:0>5.1f}".format(
3037
+ sep=separator,
3038
+ avgpower=pwr,
3039
+ )
3040
+
3041
+ stri1 += "{sep}{avgsr:2.1f}{sep}{avghr:0>5.1f}{sep}".format(
3042
+ avgsr=spm,
3043
+ sep=separator,
3044
+ avghr=avghr
3045
+ )
3046
+
3047
+ stri1 += "{maxhr:0>5.1f}{sep}{avgdps:0>4.1f}\n".format(
3048
+ sep=separator,
3049
+ maxhr=maxhr,
3050
+ avgdps=avgdps
3051
+ )
3052
+
3053
+ return stri1
3054
+
3055
+ def intervalstats(self, separator='|'):
3056
+ stri = "Workout Details\n"
3057
+ stri += "#-{sep}SDist{sep}-Split-{sep}-SPace-{sep}-Pwr--{sep}-SPM--{sep}AvgHR{sep}DPS-\n".format(
3058
+ sep=separator)
3059
+ aantal = len(self.summarydata)
3060
+ for i in self.summarydata.index:
3061
+ sdist = self.summarydata.loc[i,
3062
+ 'Total Distance (GPS)']
3063
+
3064
+ if self.dist_unit == 'km':
3065
+ sdist = float(sdist)*1000.
3066
+
3067
+ split = self.summarydata.loc[i,
3068
+ 'Total Elapsed Time']
3069
+ space = self.summarydata.loc[i,
3070
+ 'Avg Split (GPS)']
3071
+ try:
3072
+ pwr = self.summarydata.loc[i,
3073
+ 'Avg Power']
3074
+ except KeyError:
3075
+ pwr = 0 * space
3076
+
3077
+ spm = self.summarydata.loc[i,
3078
+ 'Avg Stroke Rate']
3079
+ try:
3080
+ avghr = self.summarydata.loc[i,
3081
+ 'Avg Heart Rate']
3082
+ except KeyError:
3083
+ avghr = 0 * space
3084
+
3085
+ nrstrokes = self.summarydata.loc[i,
3086
+ 'Total Strokes']
3087
+ try:
3088
+ dps = float(sdist) / float(nrstrokes)
3089
+ except ZeroDivisionError: # pragma: no cover
3090
+ dps = 0.0
3091
+
3092
+ splitstring = split
3093
+
3094
+
3095
+ newsplitstring = flexistrftime(flexistrptime(splitstring))
3096
+ pacestring = space
3097
+ newpacestring = flexistrftime(flexistrptime(pacestring))
3098
+
3099
+ stri += "{i:0>2}{sep}{sdist:0>5}{sep}{split}{sep}{space}{sep} {pwr:0>3} {sep}".format(
3100
+ i=i + 1,
3101
+ sdist=int(float(sdist)),
3102
+ split=newsplitstring,
3103
+ space=newpacestring,
3104
+ pwr=pwr,
3105
+ sep=separator,
3106
+ )
3107
+ stri += " {spm} {sep} {avghr:0>3} {sep}{dps:0>4.1f}\n".format(
3108
+ sep=separator,
3109
+ avghr=avghr,
3110
+ spm=spm,
3111
+ dps=dps,
3112
+ )
3113
+
3114
+ return stri