427 lines
10 KiB
Python
427 lines
10 KiB
Python
from utils import lbstoN
|
|
import numpy as np
|
|
from models import C2WorldClassAgePerformance
|
|
import pandas as pd
|
|
from scipy import optimize
|
|
from django.utils import timezone
|
|
|
|
nometrics = [
|
|
'originalvelo',
|
|
'cumdist',
|
|
'strokes_slsh_min',
|
|
' WorkPerStroke (joules)',
|
|
' activityIdx',
|
|
' lapIdx',
|
|
# ' pointIdx',
|
|
' WorkoutType',
|
|
' IntervalType',
|
|
' WorkoutState',
|
|
' RowingState',
|
|
' WorkoutDurationType',
|
|
' WorkoutIntervalCount',
|
|
'ergpace',
|
|
'ref',
|
|
'id',
|
|
'deltat',
|
|
'workoutid',
|
|
'totalangle',
|
|
'hr_bottom',
|
|
'x_right',
|
|
'Position',
|
|
'Extensions',
|
|
'GPS Speed',
|
|
'Split (IMP)',
|
|
'Speed (IMP)',
|
|
'driveenergy',
|
|
'Distance/Stroke (IMP)',
|
|
'Distance/Stroke (GPS)',
|
|
'Distance (IMP)',
|
|
'equivergpower',
|
|
'cum_dist.1'
|
|
]
|
|
|
|
rowingmetrics = (
|
|
('time',{
|
|
'numtype':'float',
|
|
'null':True,
|
|
'verbose_name': 'Time',
|
|
'ax_min': 0,
|
|
'ax_max': 1e5,
|
|
'mode':'both',
|
|
'type': 'basic'}),
|
|
|
|
('hr',{
|
|
'numtype':'integer',
|
|
'null':True,
|
|
'verbose_name': 'Heart Rate (bpm)',
|
|
'ax_min': 100,
|
|
'ax_max': 200,
|
|
'mode':'both',
|
|
'type': 'basic'}),
|
|
|
|
('pace',{
|
|
'numtype':'float',
|
|
'null':True,
|
|
'verbose_name': 'Pace (/500m)',
|
|
'ax_min': 1.0e3*210,
|
|
'ax_max': 1.0e3*75,
|
|
'mode':'both',
|
|
'type': 'basic'}),
|
|
|
|
('spm',{
|
|
'numtype':'float',
|
|
'null':True,
|
|
'verbose_name': 'Stroke Rate (spm)',
|
|
'ax_min': 15,
|
|
'ax_max': 45,
|
|
'mode':'both',
|
|
'type': 'basic'}),
|
|
|
|
('driveenergy',{
|
|
'numtype':'float',
|
|
'null':True,
|
|
'verbose_name': 'Work Per Stroke (J)',
|
|
'ax_min': 0,
|
|
'ax_max': 1000,
|
|
'mode':'both',
|
|
'type': 'pro'}),
|
|
|
|
('power',{
|
|
'numtype':'float',
|
|
'null':True,
|
|
'verbose_name': 'Power (W)',
|
|
'ax_min': 0,
|
|
'ax_max': 600,
|
|
'mode':'both',
|
|
'type': 'basic'}),
|
|
|
|
('averageforce',{
|
|
'numtype':'float',
|
|
'null':True,
|
|
'verbose_name': 'Average Drive Force (N)',
|
|
'ax_min': 0,
|
|
'ax_max': 1200,
|
|
'mode':'both',
|
|
'type': 'pro'}),
|
|
|
|
('peakforce',{
|
|
'numtype':'float',
|
|
'null':True,
|
|
'verbose_name': 'Peak Drive Force (N)',
|
|
'ax_min': 0,
|
|
'ax_max': 1500,
|
|
'mode':'both',
|
|
'type': 'pro'}),
|
|
|
|
('drivelength',{
|
|
'numtype':'float',
|
|
'null':True,
|
|
'verbose_name': 'Drive Length (m)',
|
|
'ax_min': 0,
|
|
'ax_max': 2.5,
|
|
'mode':'both',
|
|
'type': 'pro'}),
|
|
|
|
('forceratio',{
|
|
'numtype':'float',
|
|
'null':True,
|
|
'verbose_name': 'Average/Peak Force Ratio',
|
|
'ax_min': 0,
|
|
'ax_max': 1,
|
|
'mode':'both',
|
|
'type': 'pro'}),
|
|
|
|
('distance',{
|
|
'numtype':'float',
|
|
'null':True,
|
|
'verbose_name': 'Distance (m)',
|
|
'ax_min': 0,
|
|
'ax_max': 1e5,
|
|
'mode':'both',
|
|
'type': 'basic'}),
|
|
|
|
('cumdist',{
|
|
'numtype':'float',
|
|
'null':True,
|
|
'verbose_name': 'Cumulative Distance (m)',
|
|
'ax_min': 0,
|
|
'ax_max': 1e5,
|
|
'mode':'both',
|
|
'type': 'basic'}),
|
|
|
|
('drivespeed',{
|
|
'numtype':'float',
|
|
'null':True,
|
|
'verbose_name': 'Drive Speed (m/s)',
|
|
'ax_min': 0,
|
|
'ax_max': 4,
|
|
'default': 0,
|
|
'mode':'both',
|
|
'type': 'pro'}),
|
|
|
|
('catch',{
|
|
'numtype':'float',
|
|
'null':True,
|
|
'verbose_name': 'Catch Angle (degrees)',
|
|
'ax_min': -40,
|
|
'ax_max': -75,
|
|
'default': 0,
|
|
'mode':'water',
|
|
'type': 'pro'}),
|
|
|
|
('slip',{
|
|
'numtype':'float',
|
|
'null':True,
|
|
'verbose_name': 'Slip (degrees)',
|
|
'ax_min': 0,
|
|
'ax_max': 20,
|
|
'default': 0,
|
|
'mode':'water',
|
|
'type': 'pro'}),
|
|
|
|
('finish',{
|
|
'numtype':'float',
|
|
'null':True,
|
|
'verbose_name': 'Finish Angle (degrees)',
|
|
'ax_min': 20,
|
|
'ax_max': 55,
|
|
'default': 0,
|
|
'mode':'water',
|
|
'type': 'pro'}),
|
|
|
|
('wash',{
|
|
'numtype':'float',
|
|
'null':True,
|
|
'verbose_name': 'Wash (degrees)',
|
|
'ax_min': 0,
|
|
'ax_max': 30,
|
|
'default': 0,
|
|
'mode':'water',
|
|
'type': 'pro'}),
|
|
|
|
('peakforceangle',{
|
|
'numtype':'float',
|
|
'null':True,
|
|
'verbose_name': 'Peak Force Angle',
|
|
'ax_min': -20,
|
|
'ax_max': 20,
|
|
'default': 0,
|
|
'mode':'water',
|
|
'type': 'pro'}),
|
|
|
|
|
|
('totalangle',{
|
|
'numtype':'float',
|
|
'null':True,
|
|
'verbose_name': 'Drive Length (deg)',
|
|
'ax_min': 40,
|
|
'ax_max': 140,
|
|
'default': 0,
|
|
'mode':'water',
|
|
'type': 'pro'}),
|
|
|
|
|
|
('effectiveangle',{
|
|
'numtype':'float',
|
|
'null':True,
|
|
'verbose_name': 'Effective Drive Length (deg)',
|
|
'ax_min': 40,
|
|
'ax_max': 140,
|
|
'default': 0,
|
|
'mode':'water',
|
|
'type': 'pro'}),
|
|
|
|
('rhythm',{
|
|
'numtype':'float',
|
|
'null':True,
|
|
'verbose_name': 'Stroke Rhythm',
|
|
'ax_min': 20,
|
|
'ax_max': 55,
|
|
'default': 1.0,
|
|
'mode':'erg',
|
|
'type': 'pro'}),
|
|
|
|
('efficiency',{
|
|
'numtype':'float',
|
|
'null':True,
|
|
'verbose_name': 'OTW Efficiency (%)',
|
|
'ax_min': 0,
|
|
'ax_max': 110,
|
|
'default': 0,
|
|
'mode':'water',
|
|
'type': 'pro'}),
|
|
|
|
('distanceperstroke',{
|
|
'numtype':'float',
|
|
'null':True,
|
|
'verbose_name': 'Distance per Stroke (m)',
|
|
'ax_min': 0,
|
|
'ax_max': 15,
|
|
'default': 0,
|
|
'mode':'both',
|
|
'type': 'basic'}),
|
|
|
|
)
|
|
|
|
|
|
|
|
axesnew = [
|
|
(name,d['verbose_name'],d['ax_min'],d['ax_max'],d['type']) for name,d in rowingmetrics
|
|
]
|
|
|
|
axes = tuple(axesnew+[('None','None',0,1,'basic')])
|
|
|
|
axlabels = {ax[0]:ax[1] for ax in axes}
|
|
|
|
yaxminima = {ax[0]:ax[2] for ax in axes}
|
|
|
|
yaxmaxima = {ax[0]:ax[3] for ax in axes}
|
|
|
|
defaultfavoritecharts = (
|
|
{
|
|
'yparam1':'pace',
|
|
'yparam2':'spm',
|
|
'xparam':'time',
|
|
'plottype':'line',
|
|
'workouttype':'both',
|
|
'reststrokes':True,
|
|
'notes':"""This chart shows your pace and stroke rate versus time. """,
|
|
},
|
|
{
|
|
'yparam1':'pace',
|
|
'yparam2':'hr',
|
|
'xparam':'time',
|
|
'plottype':'line',
|
|
'workouttype':'both',
|
|
'reststrokes':True,
|
|
'notes':"""This chart shows your pace and heart rate versus time.
|
|
Heart rate values will be shown only when it is in your data, i.e.
|
|
in case you recorded your heart rate during your workout""",
|
|
},
|
|
{
|
|
'yparam1':'distanceperstroke',
|
|
'yparam2':'hr',
|
|
'xparam':'time',
|
|
'plottype':'line',
|
|
'workouttype':'otw',
|
|
'reststrokes':True,
|
|
'notes':"""This chart shows the Distance covered per stroke, and your
|
|
heart rate versus time. """,
|
|
},
|
|
{
|
|
'yparam1':'driveenergy',
|
|
'yparam2':'hr',
|
|
'xparam':'time',
|
|
'plottype':'line',
|
|
'workouttype':'ote',
|
|
'reststrokes':True,
|
|
'notes':"""This chart shows the Work per Stroke and your heart rate
|
|
plotted versus time. """,
|
|
},
|
|
{
|
|
'yparam1':'distanceperstroke',
|
|
'yparam2':'None',
|
|
'xparam':'spm',
|
|
'plottype':'scatter',
|
|
'workouttype':'otw',
|
|
'reststrokes':True,
|
|
'notes':"""This chart shows the Distance per Stroke versus stroke
|
|
stroke rate. You should see a steady decline of the Distance per Stroke
|
|
as you increase stroke rate. Typical values are > 10m for steady state
|
|
dropping to 8m for race pace in the single.""",
|
|
},
|
|
{
|
|
'yparam1':'driveenergy',
|
|
'yparam2':'None',
|
|
'xparam':'spm',
|
|
'plottype':'scatter',
|
|
'workouttype':'ote',
|
|
'reststrokes':True,
|
|
'notes':"""This chart shows the Work per Stroke versus Stroke Rate.
|
|
This value should be fairly constant across all stroke rates.""",
|
|
},
|
|
)
|
|
|
|
|
|
def calc_trimp(df,sex,hrmax,hrmin,hrftp):
|
|
if sex == 'male':
|
|
f = 1.92
|
|
else:
|
|
f = 1.67
|
|
|
|
dt = df['time'].diff()/6.e4
|
|
|
|
hrr = (df['hr']-hrmin)/(hrmax-hrmin)
|
|
hrrftp = (hrftp-hrmin)/float(hrmax-hrmin)
|
|
trimp1hr = 60*hrrftp*0.64*np.exp(f*hrrftp)
|
|
|
|
trimpdata = dt*hrr*0.64*np.exp(f*hrr)
|
|
trimp = trimpdata.sum()
|
|
|
|
hrtss = 100*trimp/trimp1hr
|
|
|
|
return trimp,hrtss
|
|
|
|
|
|
def getagegrouprecord(age,sex='male',weightcategory='hwt',
|
|
distance=2000,duration=None,indf=pd.DataFrame()):
|
|
|
|
if not indf.empty:
|
|
if not duration:
|
|
df = indf[indf['distance'] == distance]
|
|
else:
|
|
duration = 60*int(duration)
|
|
df = indf[indf['duration'] == duration]
|
|
else:
|
|
if not duration:
|
|
df = pd.DataFrame(
|
|
list(
|
|
C2WorldClassAgePerformance.objects.filter(
|
|
distance=distance,
|
|
sex=sex,
|
|
weightcategory=weightcategory
|
|
).values()
|
|
)
|
|
)
|
|
else:
|
|
duration=60*int(duration)
|
|
df = pd.DataFrame(
|
|
list(
|
|
C2WorldClassAgePerformance.objects.filter(
|
|
duration=duration,
|
|
sex=sex,
|
|
weightcategory=weightcategory
|
|
).values()
|
|
)
|
|
)
|
|
|
|
if not df.empty:
|
|
ages = df['age']
|
|
powers = df['power']
|
|
|
|
#poly_coefficients = np.polyfit(ages,powers,6)
|
|
fitfunc = lambda pars, x: np.abs(pars[0])*(1-x/max(120,pars[1]))-np.abs(pars[2])*np.exp(-x/np.abs(pars[3]))+np.abs(pars[4])*(np.sin(np.pi*x/max(50,pars[5])))
|
|
errfunc = lambda pars, x,y: fitfunc(pars,x)-y
|
|
|
|
p0 = [700,120,700,10,100,100]
|
|
|
|
try:
|
|
p1, success = optimize.leastsq(errfunc,p0[:],
|
|
args = (ages,powers))
|
|
except:
|
|
p1 = p0
|
|
success = 0
|
|
|
|
if success:
|
|
power = fitfunc(p1, float(age))
|
|
|
|
#power = np.polyval(poly_coefficients,age)
|
|
|
|
power = 0.5*(np.abs(power)+power)
|
|
else:
|
|
power = 0
|
|
else:
|
|
power = 0
|
|
|
|
return power
|