diff --git a/rowers/dataprep.py b/rowers/dataprep.py index da4ca849..22bd7a7b 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -547,6 +547,49 @@ def df_resample(datadf): newdf = datadf.groupby(['timestamps']).mean() return newdf +def resample(id,r,parent,overwrite='copy'): + data, row = getrowdata_db(id=id) + messages = [] + + # resample + startdatetime = row.startdatetime + data['datetime'] = data['time'].apply(lambda x:startdatetime+datetime.timedelta(seconds=x/1000.)) + + data = data.resample('S',on='datetime').mean() + data.interpolate(method='linear',inplace=True) + data.reset_index(drop=True,inplace=True) + + #data.drop('datetime',inplace=True) + data['pace'] = data['pace'] / 1000. + data['time'] = data['time'] / 1000. + + if overwrite=='overwrite': + # remove CP data + try: + cpfile = 'media/cpdata_{id}.parquet.gz'.format(id=parent.id) + os.remove(cpfile) + except FileNotFoundError: + pass + # save + data.rename(columns=columndict,inplace=True) + + starttimeunix = arrow.get(startdatetime).timestamp() + data[' ElapsedTime (sec)'] = data['TimeStamp (sec)'] + + data['TimeStamp (sec)'] = data['TimeStamp (sec)'] + starttimeunix + + row = rrdata(df=data) + + row.write_csv(parent.csvfilename,gzip=True) + + res = dataprep(row.df, id=parent.id, bands=True, barchart=True, otwpower=True, empower=True,inboard=parent.inboard) + else: + id, message = new_workout_from_df(r, data, title=parent.name + '(Resampled)', + parent=parent,forceunit='N') + messages.append(message) + + return data, id, messages + def clean_df_stats(datadf, workstrokesonly=True, ignorehr=True, ignoreadvanced=False): @@ -1239,6 +1282,7 @@ def setcp(workout,background=False,recurrance=True): strokesdf = getsmallrowdata_db(['power','workoutid','time'],ids = [workout.id]) + try: if strokesdf['power'].std()==0: return pd.DataFrame(),pd.Series(dtype='float'),pd.Series(dtype='float') diff --git a/rowers/datautils.py b/rowers/datautils.py index 8489af1f..ede1bfd3 100644 --- a/rowers/datautils.py +++ b/rowers/datautils.py @@ -285,26 +285,8 @@ def getcp(dfgrouped,logarr): mask = ww > 2000 ww.loc[mask] = 0 - - tmax = tt.max() - -# if tmax > 3600000: -# newlen = int(tmax/10000.) -# else: -# newlen = len(tt) -# if newlen < len(tt): -# newt = np.arange(newlen)*tmax/float(newlen) -# ww = griddata(tt.values, -# ww.values, -# newt,method='nearest', -# rescale=True) -# -# tt = pd.Series(newt) -# ww = pd.Series(ww) - - try: avgpower[id] = int(ww.mean()) except ValueError: # pragma: no cover diff --git a/rowers/forms.py b/rowers/forms.py index 1ee14629..01039f61 100644 --- a/rowers/forms.py +++ b/rowers/forms.py @@ -58,6 +58,14 @@ class FlexibleDecimalField(forms.DecimalField): value = value.replace('.', '').replace(',', '.') return super(FlexibleDecimalField, self).to_python(value) +class ResampleForm(forms.Form): + resamplechoices = ( + ('overwrite','Overwrite Workout'), + ('copy','Create a Duplicate Workout') + ) + + resamplechoice = forms.ChoiceField(initial='copy',choices=resamplechoices,label='Copy behavior') + class TrainingZonesForm(forms.Form): zoneschoices = ( ('power','Power Zones'), diff --git a/rowers/templates/menu_workout.html b/rowers/templates/menu_workout.html index 16b38908..da4f6a19 100644 --- a/rowers/templates/menu_workout.html +++ b/rowers/templates/menu_workout.html @@ -285,6 +285,12 @@  Explore Raw Data + +
  • + +  Resample to 1s + +
  • diff --git a/rowers/templates/workout_resample.html b/rowers/templates/workout_resample.html new file mode 100644 index 00000000..f794fd71 --- /dev/null +++ b/rowers/templates/workout_resample.html @@ -0,0 +1,30 @@ +{% extends "newbase.html" %} +{% load static %} +{% load rowerfilters %} + +{% block title %}Workout Data Resample{% endblock %} + +{% block main %} +

    Workout Data Resample for {{ workout.name }}

    + +
      +
    • +

      This functionality resamples the data to 1 second intervals. This can be useful in + case there are gaps larger than 30 seconds in the data, which prevent the creation + of a correct CP chart. +

      +

      +

      + {{ form.as_table }} + {% csrf_token %} + +
      +

      +
    • +
    + +{% endblock %} + +{% block sidebar %} +{% include 'menu_workout.html' %} +{% endblock %} diff --git a/rowers/urls.py b/rowers/urls.py index 71254ad5..84710116 100644 --- a/rowers/urls.py +++ b/rowers/urls.py @@ -384,6 +384,8 @@ urlpatterns = [ re_path(r'^workout/(?P\b[0-9A-Fa-f]+\b)/stats/$',views.workout_stats_view,name='workout_stats_view'), re_path(r'^workout/(?P\b[0-9A-Fa-f]+\b)/data/$',views.workout_data_view, name='workout_data_view'), + re_path(r'^workout/(?P\b[0-9A-Fa-f]+\b)/resample/$',views.workout_resample_view, + name='workout_resample_view'), re_path(r'^workout/(?P\b[0-9A-Fa-f]+\b)/(?P\w+)/erase/$',views.workout_erase_column_view, name='workout_erase_column_view'), re_path(r'^workout/(?P\b[0-9A-Fa-f]+\b)/zeropower-confirm/$',views.remove_power_confirm_view, diff --git a/rowers/views/statements.py b/rowers/views/statements.py index 98071518..649c0e13 100644 --- a/rowers/views/statements.py +++ b/rowers/views/statements.py @@ -64,7 +64,7 @@ from django.contrib.auth import authenticate, login, logout from rowers.forms import ( ForceCurveOptionsForm,HistoForm,TeamMessageForm, LoginForm,DocumentsForm,UploadOptionsForm,ImageForm,CourseForm, - CourseConfirmForm, + CourseConfirmForm,ResampleForm, TeamUploadOptionsForm,WorkFlowLeftPanelForm,WorkFlowMiddlePanelForm, WorkFlowLeftPanelElement,WorkFlowMiddlePanelElement, LandingPageForm,PlannedSessionSelectForm,WorkoutSessionSelectForm, diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py index d633e836..44829b37 100644 --- a/rowers/views/workoutviews.py +++ b/rowers/views/workoutviews.py @@ -3131,7 +3131,54 @@ def workout_erase_column_view(request, id=0,column=''): +# resample to 1s intervals +@permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True) +def workout_resample_view(request, id=0): + r = getrower(request.user) + w = get_workoutuser(id, request) + form = ResampleForm() + + if request.method == 'POST': + form = ResampleForm(request.POST) + if form.is_valid(): + overwrite = form.cleaned_data['resamplechoice'] + datadf,id, msgs = dataprep.resample(encoder.decode_hex(id),r,w,overwrite=overwrite) + + for message in msgs: + messages.info(request,message) + + + url = get_workout_default_page(request,encoder.encode_hex(id)) + + messages.info(request,'The workout has been resampled: here'.format(url=url)) + + breadcrumbs = [ + { + 'url':'/rowers/list-workouts/', + 'name':'Workouts' + }, + { + 'url':get_workout_default_page(request,id), + 'name': w.name + }, + { + 'url':reverse('workout_resample_view',kwargs={'id':id}), + 'name': 'Resample Data' + } + + ] + + return render(request, + 'workout_resample.html', + { + 'form':form, + 'teams':get_my_teams(request.user), + 'workout': w, + 'breadcrumbs': breadcrumbs, + + } + ) # data explorer @permission_required('workout.change_workout',fn=get_workout_by_opaqueid,raise_exception=True)