Private
Public Access
1
0

Merge branch 'develop' into feature/restapi

This commit is contained in:
Sander Roosendaal
2016-12-13 16:41:14 +01:00
5 changed files with 345 additions and 78 deletions

View File

@@ -1,3 +1,4 @@
from rowers.models import Workout, User, Rower, WorkoutForm,RowerForm,GraphImage from rowers.models import Workout, User, Rower, WorkoutForm,RowerForm,GraphImage
from rowingdata import rower as rrower from rowingdata import rower as rrower
from rowingdata import main as rmain from rowingdata import main as rmain
@@ -124,7 +125,177 @@ def tailwind(bearing,vwind,winddir):
from rowers.dataprep import nicepaceformat,niceformat from rowers.dataprep import nicepaceformat,niceformat
from rowers.dataprep import timedeltaconv from rowers.dataprep import timedeltaconv
def interactive_forcecurve(theworkouts):
TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,resize,crosshair'
ids = [int(w.id) for w in theworkouts]
boattype = theworkouts[0].boattype
columns = ['catch','slip','wash','finish','averageforce',
'peakforceangle','peakforce','spm','distance']
rowdata = dataprep.getsmallrowdata_db(columns,ids=ids)
catchav = rowdata['catch'].mean()
finishav = rowdata['finish'].mean()
washav = rowdata['wash'].mean()
slipav = rowdata['slip'].mean()
peakforceav = rowdata['peakforce'].mean()
averageforceav = rowdata['averageforce'].mean()
peakforceangleav = rowdata['peakforceangle'].mean()
x = [catchav,
catchav+slipav,
peakforceangleav,
finishav-washav,
finishav]
thresholdforce = 100 if 'x' in boattype else 200
thresholdforce /= 4.45 # N to lbs
y = [0,thresholdforce,
peakforceav,
thresholdforce,0]
source = ColumnDataSource(
data = dict(
x = x,
y = y,
))
source2 = ColumnDataSource(
rowdata
)
plot = Figure(tools=TOOLS,
toolbar_sticky=False)
avf = Span(location=averageforceav,dimension='width',line_color='blue',
line_dash=[6,6],line_width=2)
plot.line('x','y',source=source,color="red")
plot.add_layout(avf)
plot.xaxis.axis_label = "Angle"
plot.yaxis.axis_label = "Force (lbs)"
plot.title.text = theworkouts[0].name
plot.title.text_font_size=value("1.0em")
yrange1 = Range1d(start=0,end=200)
plot.y_range = yrange1
xrange1 = Range1d(start=yaxmaxima['catch'],end=yaxmaxima['finish'])
plot.x_range = xrange1
callback = CustomJS(args = dict(
source=source,
source2=source2,
avf=avf,
), code="""
var data = source.data
var data2 = source2.data
var x = data['x']
var y = data['y']
var spm1 = data2['spm']
var distance1 = data2['distance']
var thresholdforce = y[1]
var c = source2.data['catch']
var finish = data2['finish']
var slip = data2['slip']
var wash = data2['wash']
var peakforceangle = data2['peakforceangle']
var peakforce = data2['peakforce']
var averageforce = data2['averageforce']
var minspm = minspm.value
var maxspm = maxspm.value
var mindist = mindist.value
var maxdist = maxdist.value
var catchav = 0
var finishav = 0
var slipav = 0
var washav = 0
var peakforceangleav = 0
var averageforceav = 0
var peakforceav = 0
var count = 0
for (i=0; i<c.length; i++) {
if (spm1[i]>=minspm && spm1[i]<=maxspm) {
if (distance1[i]>=mindist && distance1[i]<=maxdist) {
catchav += c[i]
finishav += finish[i]
slipav += slip[i]
washav += wash[i]
peakforceangleav += peakforceangle[i]
averageforceav += averageforce[i]
peakforceav += peakforce[i]
count += 1
}
}
}
catchav /= count
finishav /= count
slipav /= count
washav /= count
peakforceangleav /= count
peakforceav /= count
averageforceav /= count
data['x'] = [catchav,catchav+slipav,peakforceangleav,finishav-washav,finishav]
data['y'] = [0,thresholdforce,peakforceav,thresholdforce,0]
avf.location = averageforceav
source.trigger('change');
""")
slider_spm_min = Slider(start=15.0, end=55,value=15.0, step=.1,
title="Min SPM",callback=callback)
callback.args["minspm"] = slider_spm_min
slider_spm_max = Slider(start=15.0, end=55,value=55.0, step=.1,
title="Max SPM",callback=callback)
callback.args["maxspm"] = slider_spm_max
distmax = 100+100*int(rowdata['distance'].max()/100.)
slider_dist_min = Slider(start=0,end=distmax,value=0,step=1,
title="Min Distance",callback=callback)
callback.args["mindist"] = slider_dist_min
slider_dist_max = Slider(start=0,end=distmax,value=distmax,
step=1,
title="Max Distance",callback=callback)
callback.args["maxdist"] = slider_dist_max
layout = layoutrow([layoutcolumn([slider_spm_min,
slider_spm_max,
slider_dist_min,
slider_dist_max,
],
),
plot])
script, div = components(layout)
js_resources = INLINE.render_js()
css_resources = INLINE.render_css()
return [script,div,js_resources,css_resources]
def interactive_histoall(theworkouts): def interactive_histoall(theworkouts):
TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,resize,crosshair' TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,resize,crosshair'

View File

@@ -0,0 +1,57 @@
{% extends "base.html" %}
{% load staticfiles %}
{% load rowerfilters %}
{% load tz %}
{% block title %} Force Curve Plot {% endblock %}
{% localtime on %}
{% block content %}
{{ js_res | safe }}
{{ css_res| safe }}
<script type="text/javascript" src="/static/js/bokeh-0.12.3.min.js"></script>
<script type="text/javascript" src="/static/js/bokeh-widgets-0.12.3.min.js"></script>
<script async="true" type="text/javascript">
Bokeh.set_log_level("info");
</script>
{{ the_script |safe }}
<style>
/* Need this to get the page in "desktop mode"; not having an infinite height.*/
html, body {height: 100%; margin:5px;}
</style>
<div id="navigation" class="grid_12 alpha">
{% if user.is_authenticated and mayedit %}
<div class="grid_2 alpha">
<p>
<a class="button gray small" href="/rowers/workout/{{ id }}/edit">Edit Workout</a>
</p>
</div>
<div class="grid_2 suffix_8 omega">
<p>
<a class="button gray small" href="/rowers/workout/{{ id }}/advanced">Advanced Edit</a>
</p>
</div>
{% endif %}
</div>
<p>&nbsp;</p>
<div id="theplot" class="grid_12 alpha">
{{ the_div|safe }}
</div>
{% endblock %}
{% endlocaltime %}

View File

@@ -26,77 +26,79 @@
<h1>My Workouts</h1> <h1>My Workouts</h1>
{% if workouts %} {% if workouts %}
<table width="70%" class="listtable"> <table width="70%" class="listtable">
<thead> <thead>
<tr> <tr>
<th> Date</th> <th> Date</th>
<th> Time</th> <th> Time</th>
<th> Name</th> <th> Name</th>
<th> Type</th> <th> Type</th>
<th> Distance </th> <th> Distance </th>
<th> Duration </th> <th> Duration </th>
<th> Avg HR </th> <th> Avg HR </th>
<th> Max HR </th> <th> Max HR </th>
<th> Delete</th> <th> Delete</th>
<th> Export</th> <th> Export</th>
</tr> </tr>
</thead> </thead>
</tbody> </tbody>
{% for workout in workouts %} {% for workout in workouts %}
<tr> <tr>
<td> {{ workout.date }} </td> <td> {{ workout.date }} </td>
<td> {{ workout.starttime }} </td> <td> {{ workout.starttime }} </td>
<td> <td>
{% if user.rower.rowerplan == 'pro' %} {% if user.rower.rowerplan == 'pro' %}
<a href="/rowers/workout/{{ workout.id }}/edit">{{ workout.name }}</a> </td> <a href="/rowers/workout/{{ workout.id }}/edit">{{ workout.name }}</a> </td>
{% else %} {% else %}
<a href="/rowers/workout/{{ workout.id }}/edit">{{ workout.name }}</a> </td> <a href="/rowers/workout/{{ workout.id }}/edit">{{ workout.name }}</a> </td>
{% endif %} {% endif %}
<td> {{ workout.workouttype }} </td> <td> {{ workout.workouttype }} </td>
<td> {{ workout.distance }}m</td> <td> {{ workout.distance }}m</td>
<td> {{ workout.duration |durationprint:"%H:%M:%S.%f" }} </td> <td> {{ workout.duration |durationprint:"%H:%M:%S.%f" }} </td>
<td> {{ workout.averagehr }} </td> <td> {{ workout.averagehr }} </td>
<td> {{ workout.maxhr }} </td> <td> {{ workout.maxhr }} </td>
<td> <a class="button red small" href="/rowers/workout/{{ workout.id }}/deleteconfirm">Delete</td> <td> <a class="button red small" href="/rowers/workout/{{ workout.id }}/deleteconfirm">Delete</td>
<td> <a class="button blue small" href="/rowers/workout/{{ workout.id }}/export">Export</a> </td> <td> <a class="button blue small" href="/rowers/workout/{{ workout.id }}/export">Export</a> </td>
<td> <a class="button blue small" href="/rowers/workout/{{ workout.id }}/flexchart">Flex</a> </td>
</tr>
</tr>
{% endfor %}
</tbody> {% endfor %}
</table> </tbody>
{% else %} </table>
<p> No workouts found </p> {% else %}
{% endif %} <p> No workouts found </p>
{% endif %}
</div> </div>
<div class="grid_6 alpha"> <div class="grid_6 alpha">
<form id="searchform" action="/rowers/list-workouts/{{ startdate|date:"Y-m-d" }}/{{ enddate|date:"Y-m-d" }}" <form id="searchform" action="/rowers/list-workouts/{{ startdate|date:"Y-m-d" }}/{{ enddate|date:"Y-m-d" }}"
method="get" accept-charset="utf-8"> method="get" accept-charset="utf-8">
<div class="grid_3 prefix_1 alpha"> <div class="grid_3 prefix_1 alpha">
<input class="searchfield" id="searchbox" name="q" type="text" placeholder="Search"> <input class="searchfield" id="searchbox" name="q" type="text" placeholder="Search">
</div> </div>
<div class="grid_1 suffix_1 omega"> <div class="grid_1 suffix_1 omega">
<button class="button blue small" type="submit"> <button class="button blue small" type="submit">
Search Search
</button> </button>
</div> </div>
</form> </form>
</div> </div>
<div class="grid_2 prefix_3 omega"> <div class="grid_2 prefix_3 omega">
<span class="button gray small"> <span class="button gray small">
{% if workouts.has_previous %} {% if workouts.has_previous %}
<a class="wh" href="?page={{ workouts.previous_page_number }}">&lt;</a> <a class="wh" href="?page={{ workouts.previous_page_number }}">&lt;</a>
{% endif %} {% endif %}
<span> <span>
Page {{ workouts.number }} of {{ workouts.paginator.num_pages }}. Page {{ workouts.number }} of {{ workouts.paginator.num_pages }}.
</span> </span>
{% if workouts.has_next %} {% if workouts.has_next %}
<a class="wh" href="?page={{ workouts.next_page_number }}">&gt;</a> <a class="wh" href="?page={{ workouts.next_page_number }}">&gt;</a>
{% endif %} {% endif %}
</span> </span>
</div>
{% endblock %} {% endblock %}

View File

@@ -82,6 +82,7 @@ urlpatterns = [
url(r'^workout/upload/$',views.workout_upload_view), url(r'^workout/upload/$',views.workout_upload_view),
url(r'^workout/upload/(.+.*)$',views.workout_upload_view), url(r'^workout/upload/(.+.*)$',views.workout_upload_view),
url(r'^workout/(?P<id>\d+)/histo$',views.workout_histo_view), url(r'^workout/(?P<id>\d+)/histo$',views.workout_histo_view),
url(r'^workout/(?P<id>\d+)/forcecurve$',views.workout_forcecurve_view),
url(r'^workout/(?P<id>\d+)/export/c/(?P<message>\w+.*)/s/(?P<successmessage>\w+.*)$',views.workout_export_view), url(r'^workout/(?P<id>\d+)/export/c/(?P<message>\w+.*)/s/(?P<successmessage>\w+.*)$',views.workout_export_view),
url(r'^workout/(?P<id>\d+)/export/c/(?P<message>\w+.*)$',views.workout_export_view), url(r'^workout/(?P<id>\d+)/export/c/(?P<message>\w+.*)$',views.workout_export_view),
url(r'^workout/(?P<id>\d+)/export/s/(?P<successmessage>\w+.*)$',views.workout_export_view), url(r'^workout/(?P<id>\d+)/export/s/(?P<successmessage>\w+.*)$',views.workout_export_view),

View File

@@ -1394,6 +1394,35 @@ def cum_flex(request,theuser=0,
'promember':promember, 'promember':promember,
}) })
@user_passes_test(promember,login_url="/",redirect_field_name=None)
def workout_forcecurve_view(request,id=0):
row = Workout.objects.get(id=id)
promember=0
mayedit=0
if not request.user.is_anonymous():
r = Rower.objects.get(user=request.user)
result = request.user.is_authenticated() and r.rowerplan=='pro'
if result:
promember=1
if request.user == row.user.user:
mayedit=1
if not promember:
return HttpResponseRedirect("/rowers/about/")
script,div,js_resources,css_resources = interactive_forcecurve([row])
return render(request,
'forcecurve_single.html',
{
'the_script':script,
'the_div':div,
'js_res': js_resources,
'css_res':css_resources,
'id':id,
'mayedit':mayedit,
})
@login_required() @login_required()
def workout_histo_view(request,id=0): def workout_histo_view(request,id=0):
row = Workout.objects.get(id=id) row = Workout.objects.get(id=id)
@@ -1410,9 +1439,6 @@ def workout_histo_view(request,id=0):
if not promember: if not promember:
return HttpResponseRedirect("/rowers/about/") return HttpResponseRedirect("/rowers/about/")
res = interactive_histoall([row]) res = interactive_histoall([row])
script = res[0] script = res[0]
div = res[1] div = res[1]
@@ -2612,8 +2638,8 @@ def workout_flexchart3_view(request,*args,**kwargs):
result = request.user.is_authenticated() and r.rowerplan=='pro' result = request.user.is_authenticated() and r.rowerplan=='pro'
if result: if result:
promember=1 promember=1
if request.user == row.user.user: if request.user == row.user.user:
mayedit=1 mayedit=1
workouttype = 'ote' workouttype = 'ote'
@@ -2692,10 +2718,20 @@ def workout_flexchart3_view(request,*args,**kwargs):
workstrokesonly = False workstrokesonly = False
# create interactive plot # create interactive plot
script,div,js_resources,css_resources = interactive_flex_chart2(id,xparam=xparam,yparam1=yparam1, try:
script,div,js_resources,css_resources = interactive_flex_chart2(id,xparam=xparam,yparam1=yparam1,
yparam2=yparam2, yparam2=yparam2,
promember=promember,plottype=plottype, promember=promember,plottype=plottype,
workstrokesonly=workstrokesonly) workstrokesonly=workstrokesonly)
except ValueError:
script,div = interactive_flex_chart2(id,xparam=xparam,yparam1=yparam1,
yparam2=yparam2,
promember=promember,plottype=plottype,
workstrokesonly=workstrokesonly)
js_resources = ""
css_resources = ""
# script = res[0] # script = res[0]
# div = res[1] # div = res[1]
# js_resources = res[2] # js_resources = res[2]
@@ -2847,8 +2883,8 @@ def workout_biginteractive_view(request,id=0,message="",successmessage=""):
result = request.user.is_authenticated() and r.rowerplan=='pro' result = request.user.is_authenticated() and r.rowerplan=='pro'
if result: if result:
promember=1 promember=1
if request.user == row.user.user: if request.user == row.user.user:
mayedit=1 mayedit=1
# create interactive plot # create interactive plot
@@ -2883,8 +2919,8 @@ def workout_otwpowerplot_view(request,id=0,message="",successmessage=""):
result = request.user.is_authenticated() and r.rowerplan=='pro' result = request.user.is_authenticated() and r.rowerplan=='pro'
if result: if result:
promember=1 promember=1
if request.user == row.user.user: if request.user == row.user.user:
mayedit=1 mayedit=1
# create interactive plot # create interactive plot
res = interactive_otw_advanced_pace_chart(id,promember=promember) res = interactive_otw_advanced_pace_chart(id,promember=promember)
@@ -3638,10 +3674,10 @@ def workout_upload_view(request,message=""):
f1 = res[0] # file name f1 = res[0] # file name
f2 = res[1] # file name incl media directory f2 = res[1] # file name incl media directory
# new # new
fileformat = get_file_type(f2) fileformat = get_file_type(f2)
if fileformat == 'unknown': if fileformat == 'unknown':
message = "We couldn't recognize the file type" message = "We couldn't recognize the file type"
url = reverse(workout_upload_view, url = reverse(workout_upload_view,
@@ -3695,6 +3731,10 @@ def workout_upload_view(request,message=""):
# handle speed coach GPS 2 # handle speed coach GPS 2
if (fileformat == 'speedcoach2'): if (fileformat == 'speedcoach2'):
row = SpeedCoach2Parser(f2) row = SpeedCoach2Parser(f2)
try:
summary = row.allstats()
except:
pass
# handle ErgStick # handle ErgStick
if (fileformat == 'ergstick'): if (fileformat == 'ergstick'):
@@ -3728,13 +3768,12 @@ def workout_upload_view(request,message=""):
if row == 0: if row == 0:
return HttpResponse("Error: CSV Data File Not Found") return HttpResponse("Error: CSV Data File Not Found")
# auto smoothing # auto smoothing
pace = row.df[' Stroke500mPace (sec/500m)'].values pace = row.df[' Stroke500mPace (sec/500m)'].values
velo = 500./pace velo = 500./pace
f = row.df['TimeStamp (sec)'].diff().mean() f = row.df['TimeStamp (sec)'].diff().mean()
windowsize = 2*(int(10./(f)))+1 windowsize = 2*(int(10./(f)))+1
if not 'originalvelo' in row.df: if not 'originalvelo' in row.df:
row.df['originalvelo'] = velo row.df['originalvelo'] = velo
@@ -3746,9 +3785,9 @@ def workout_upload_view(request,message=""):
velo3 = pd.Series(velo2) velo3 = pd.Series(velo2)
velo3 = velo3.replace([-np.inf,np.inf],np.nan) velo3 = velo3.replace([-np.inf,np.inf],np.nan)
velo3 = velo3.fillna(method='ffill') velo3 = velo3.fillna(method='ffill')
pace2 = 500./abs(velo3)
pace2 = 500./abs(velo3)
row.df[' Stroke500mPace (sec/500m)'] = pace2 row.df[' Stroke500mPace (sec/500m)'] = pace2
row.df = row.df.fillna(0) row.df = row.df.fillna(0)
@@ -3758,7 +3797,6 @@ def workout_upload_view(request,message=""):
os.remove(f2) os.remove(f2)
except: except:
pass pass
# recalculate power data # recalculate power data
if workouttype == 'rower' or workouttype == 'dynamic' or workouttype == 'slides': if workouttype == 'rower' or workouttype == 'dynamic' or workouttype == 'slides':
@@ -3769,19 +3807,18 @@ def workout_upload_view(request,message=""):
except: except:
pass pass
if fileformat != 'fit': if fileformat != 'fit' and summary == '':
summary = row.summary() summary = row.summary()
summary += '\n' summary += '\n'
summary += row.intervalstats_painsled() summary += row.intervalstats_painsled()
averagehr = row.df[' HRCur (bpm)'].mean() averagehr = row.df[' HRCur (bpm)'].mean()
maxhr = row.df[' HRCur (bpm)'].max() maxhr = row.df[' HRCur (bpm)'].max()
totaldist = row.df['cum_dist'].max() totaldist = row.df['cum_dist'].max()
totaltime = row.df['TimeStamp (sec)'].max()-row.df['TimeStamp (sec)'].min() totaltime = row.df['TimeStamp (sec)'].max()-row.df['TimeStamp (sec)'].min()
totaltime = totaltime+row.df.ix[0,' ElapsedTime (sec)'] totaltime = totaltime+row.df.ix[0,' ElapsedTime (sec)']
hours = int(totaltime/3600.) hours = int(totaltime/3600.)
minutes = int((totaltime - 3600.*hours)/60.) minutes = int((totaltime - 3600.*hours)/60.)
seconds = int(totaltime - 3600.*hours - 60.*minutes) seconds = int(totaltime - 3600.*hours - 60.*minutes)
@@ -3802,7 +3839,6 @@ def workout_upload_view(request,message=""):
if (len(ws) != 0): if (len(ws) != 0):
message = "Warning: This workout probably already exists in the database" message = "Warning: This workout probably already exists in the database"
w = Workout(user=r,name=t,date=workoutdate, w = Workout(user=r,name=t,date=workoutdate,
workouttype=workouttype, workouttype=workouttype,
duration=duration,distance=totaldist, duration=duration,distance=totaldist,