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 rowingdata import rower as rrower
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 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):
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>
{% if workouts %}
<table width="70%" class="listtable">
<thead>
<tr>
<th> Date</th>
<th> Time</th>
<th> Name</th>
<th> Type</th>
<th> Distance </th>
<th> Duration </th>
<th> Avg HR </th>
<th> Max HR </th>
<th> Delete</th>
<th> Export</th>
</tr>
</thead>
</tbody>
{% for workout in workouts %}
<tr>
<td> {{ workout.date }} </td>
<td> {{ workout.starttime }} </td>
<td>
{% if user.rower.rowerplan == 'pro' %}
<a href="/rowers/workout/{{ workout.id }}/edit">{{ workout.name }}</a> </td>
{% else %}
<a href="/rowers/workout/{{ workout.id }}/edit">{{ workout.name }}</a> </td>
{% endif %}
<td> {{ workout.workouttype }} </td>
<td> {{ workout.distance }}m</td>
<td> {{ workout.duration |durationprint:"%H:%M:%S.%f" }} </td>
<td> {{ workout.averagehr }} </td>
<td> {{ workout.maxhr }} </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>
<table width="70%" class="listtable">
<thead>
<tr>
<th> Date</th>
<th> Time</th>
<th> Name</th>
<th> Type</th>
<th> Distance </th>
<th> Duration </th>
<th> Avg HR </th>
<th> Max HR </th>
<th> Delete</th>
<th> Export</th>
</tr>
</thead>
</tbody>
{% for workout in workouts %}
<tr>
<td> {{ workout.date }} </td>
<td> {{ workout.starttime }} </td>
<td>
{% if user.rower.rowerplan == 'pro' %}
<a href="/rowers/workout/{{ workout.id }}/edit">{{ workout.name }}</a> </td>
{% else %}
<a href="/rowers/workout/{{ workout.id }}/edit">{{ workout.name }}</a> </td>
{% endif %}
<td> {{ workout.workouttype }} </td>
<td> {{ workout.distance }}m</td>
<td> {{ workout.duration |durationprint:"%H:%M:%S.%f" }} </td>
<td> {{ workout.averagehr }} </td>
<td> {{ workout.maxhr }} </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 }}/flexchart">Flex</a> </td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p> No workouts found </p>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p> No workouts found </p>
{% endif %}
</div>
<div class="grid_6 alpha">
<form id="searchform" action="/rowers/list-workouts/{{ startdate|date:"Y-m-d" }}/{{ enddate|date:"Y-m-d" }}"
method="get" accept-charset="utf-8">
<div class="grid_3 prefix_1 alpha">
<input class="searchfield" id="searchbox" name="q" type="text" placeholder="Search">
</div>
<div class="grid_1 suffix_1 omega">
<button class="button blue small" type="submit">
Search
</button>
</div>
</form>
<form id="searchform" action="/rowers/list-workouts/{{ startdate|date:"Y-m-d" }}/{{ enddate|date:"Y-m-d" }}"
method="get" accept-charset="utf-8">
<div class="grid_3 prefix_1 alpha">
<input class="searchfield" id="searchbox" name="q" type="text" placeholder="Search">
</div>
<div class="grid_1 suffix_1 omega">
<button class="button blue small" type="submit">
Search
</button>
</div>
</form>
</div>
<div class="grid_2 prefix_3 omega">
<span class="button gray small">
{% if workouts.has_previous %}
<a class="wh" href="?page={{ workouts.previous_page_number }}">&lt;</a>
{% endif %}
<span>
Page {{ workouts.number }} of {{ workouts.paginator.num_pages }}.
</span>
{% if workouts.has_next %}
<a class="wh" href="?page={{ workouts.next_page_number }}">&gt;</a>
{% endif %}
</span>
</span>
</div>
{% 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/(?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+.*)$',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,
})
@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()
def workout_histo_view(request,id=0):
row = Workout.objects.get(id=id)
@@ -1410,9 +1439,6 @@ def workout_histo_view(request,id=0):
if not promember:
return HttpResponseRedirect("/rowers/about/")
res = interactive_histoall([row])
script = res[0]
div = res[1]
@@ -2612,8 +2638,8 @@ def workout_flexchart3_view(request,*args,**kwargs):
result = request.user.is_authenticated() and r.rowerplan=='pro'
if result:
promember=1
if request.user == row.user.user:
mayedit=1
if request.user == row.user.user:
mayedit=1
workouttype = 'ote'
@@ -2692,10 +2718,20 @@ def workout_flexchart3_view(request,*args,**kwargs):
workstrokesonly = False
# 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,
promember=promember,plottype=plottype,
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]
# div = res[1]
# 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'
if result:
promember=1
if request.user == row.user.user:
mayedit=1
if request.user == row.user.user:
mayedit=1
# 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'
if result:
promember=1
if request.user == row.user.user:
mayedit=1
if request.user == row.user.user:
mayedit=1
# create interactive plot
res = interactive_otw_advanced_pace_chart(id,promember=promember)
@@ -3638,10 +3674,10 @@ def workout_upload_view(request,message=""):
f1 = res[0] # file name
f2 = res[1] # file name incl media directory
# new
fileformat = get_file_type(f2)
if fileformat == 'unknown':
message = "We couldn't recognize the file type"
url = reverse(workout_upload_view,
@@ -3695,6 +3731,10 @@ def workout_upload_view(request,message=""):
# handle speed coach GPS 2
if (fileformat == 'speedcoach2'):
row = SpeedCoach2Parser(f2)
try:
summary = row.allstats()
except:
pass
# handle ErgStick
if (fileformat == 'ergstick'):
@@ -3728,13 +3768,12 @@ def workout_upload_view(request,message=""):
if row == 0:
return HttpResponse("Error: CSV Data File Not Found")
# auto smoothing
pace = row.df[' Stroke500mPace (sec/500m)'].values
# auto smoothing
pace = row.df[' Stroke500mPace (sec/500m)'].values
velo = 500./pace
f = row.df['TimeStamp (sec)'].diff().mean()
windowsize = 2*(int(10./(f)))+1
if not 'originalvelo' in row.df:
row.df['originalvelo'] = velo
@@ -3746,9 +3785,9 @@ def workout_upload_view(request,message=""):
velo3 = pd.Series(velo2)
velo3 = velo3.replace([-np.inf,np.inf],np.nan)
velo3 = velo3.fillna(method='ffill')
pace2 = 500./abs(velo3)
pace2 = 500./abs(velo3)
row.df[' Stroke500mPace (sec/500m)'] = pace2
row.df = row.df.fillna(0)
@@ -3758,7 +3797,6 @@ def workout_upload_view(request,message=""):
os.remove(f2)
except:
pass
# recalculate power data
if workouttype == 'rower' or workouttype == 'dynamic' or workouttype == 'slides':
@@ -3769,19 +3807,18 @@ def workout_upload_view(request,message=""):
except:
pass
if fileformat != 'fit':
if fileformat != 'fit' and summary == '':
summary = row.summary()
summary += '\n'
summary += row.intervalstats_painsled()
averagehr = row.df[' HRCur (bpm)'].mean()
maxhr = row.df[' HRCur (bpm)'].max()
totaldist = row.df['cum_dist'].max()
totaltime = row.df['TimeStamp (sec)'].max()-row.df['TimeStamp (sec)'].min()
totaltime = totaltime+row.df.ix[0,' ElapsedTime (sec)']
hours = int(totaltime/3600.)
minutes = int((totaltime - 3600.*hours)/60.)
seconds = int(totaltime - 3600.*hours - 60.*minutes)
@@ -3802,7 +3839,6 @@ def workout_upload_view(request,message=""):
if (len(ws) != 0):
message = "Warning: This workout probably already exists in the database"
w = Workout(user=r,name=t,date=workoutdate,
workouttype=workouttype,
duration=duration,distance=totaldist,