From f53770fcde55b1cedc075b769c05daeaa6f8ebb1 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Mon, 14 Feb 2022 15:53:35 +0100
Subject: [PATCH] adding resample function
---
rowers/dataprep.py | 44 ++++++++++++++++++++++++
rowers/datautils.py | 18 ----------
rowers/forms.py | 8 +++++
rowers/templates/menu_workout.html | 6 ++++
rowers/templates/workout_resample.html | 30 ++++++++++++++++
rowers/urls.py | 2 ++
rowers/views/statements.py | 2 +-
rowers/views/workoutviews.py | 47 ++++++++++++++++++++++++++
8 files changed, 138 insertions(+), 19 deletions(-)
create mode 100644 rowers/templates/workout_resample.html
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.
+
+
+
+
+
+
+
+{% 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)