Private
Public Access
1
0
This commit is contained in:
Sander Roosendaal
2022-07-18 19:40:47 +02:00
parent d799de2cd2
commit b557de73a2
10 changed files with 2351 additions and 1989 deletions

File diff suppressed because it is too large Load Diff

2080
rowers/dataroutines.py Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -148,6 +148,20 @@ class InstantPlanSelectForm(forms.Form):
self.fields['enddate'].initial = timezone.now(
)+datetime.timedelta(days=instantplan.duration)
# Instroke Metrics interactive chart form
class InstrokeForm(forms.Form):
metric = forms.ChoiceField(label='metric',choices=(('a','a'),('b','b')))
spm_min = forms.IntegerField(initial=15,label='SPM Min')
spm_max = forms.IntegerField(initial=45,label='SPM Max')
def __init__(self, *args, **kwargs): # pragma: no cover
choices = kwargs.pop('choices', [])
super(InstrokeForm, self).__init__(*args, **kwargs)
if len(choices) > 0:
choices = [(choice, choice) for choice in choices]
self.fields['metric'].choices = choices
self.fields['metric'].initial = choices[0]
# Video Analysis creation form
class VideoAnalysisCreateForm(forms.Form):

View File

@@ -4070,6 +4070,107 @@ def interactive_streamchart(id=0, promember=0):
return [script, div]
def instroke_interactive_chart(df,metric, workout, spm_min, spm_max):
df_pos = (df+abs(df))/2.
df_min = -(-df+abs(-df))/2.
mean_vals = df.median()
q75 = df_pos.quantile(q=0.75).replace(0,np.nan)
q25 = df_pos.quantile(q=0.25).replace(0,np.nan)
q75min = df_min.quantile(q=0.75).replace(0,np.nan)
q25min = df_min.quantile(q=0.25).replace(0,np.nan)
xvals = np.arange(len(mean_vals))
df_plot = pd.DataFrame({
'x':xvals,
'median':mean_vals,
'high':q75,
'low':q75min,
'high 2':q25min,
'low 2': q25,
})
df_plot['high'].update(df_plot.pop('high 2'))
df_plot['low'].update(df_plot.pop('low 2'))
df_plot.interpolate(axis=1,inplace=True)
TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,crosshair'
plot = Figure(plot_width=920,tools=TOOLS,
toolbar_location='above',
toolbar_sticky=False)
plot.sizing_mode = 'stretch_both'
plot.title.text = str(workout) + ' - ' + metric
# add watermark
watermarkurl = "/static/img/logo7.png"
watermarkrange = Range1d(start=0, end=1)
watermarkalpha = 0.6
watermarkx = 0.99
watermarky = 0.01
watermarkw = 184
watermarkh = 35
watermarkanchor = 'bottom_right'
plot.extra_y_ranges = {"watermark": watermarkrange}
plot.extra_x_ranges = {"watermark": watermarkrange}
plot.image_url([watermarkurl], watermarkx, watermarky,
watermarkw, watermarkh,
global_alpha=watermarkalpha,
w_units='screen',
h_units='screen',
anchor=watermarkanchor,
dilate=True,
x_range_name="watermark",
y_range_name="watermark",
)
source = ColumnDataSource(
df_plot
)
TIPS = OrderedDict([
('x','@x'),
('median','@median'),
('high','@high'),
('low','@low')
])
hover = plot.select(type=HoverTool)
hover.tooltips = TIPS
s = 'SPM: {spm_min} - {spm_max}'.format(
spm_min = spm_min,
spm_max = spm_max,
)
label = Label(x=50, y=450, x_units='screen',y_units='screen',
text=s,
background_fill_alpha=0.7,
background_fill_color='white',
text_color='black',
)
plot.add_layout(label)
plot.varea('x', y1='high', y2='low',source=source,fill_color="lightgray",alpha=0.5)
plot.line('x','median',source=source,legend_label='median',color="black",
line_width=3)
plot.add_tools(HoverTool(tooltips=TIPS))
script, div = components(plot)
return (script, div)
def interactive_chart(id=0, promember=0, intervaldata={}):

View File

@@ -14,7 +14,8 @@ from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()
from rowers.models import (
Workout, GeoPolygon, GeoPoint, GeoCourse,
VirtualRaceResult, CourseTestResult, Rower
VirtualRaceResult, CourseTestResult, Rower,
GraphImage,
)
import math
@@ -45,6 +46,7 @@ import re
import sys
import json
import traceback
from time import strftime
from scipy import optimize
from scipy.signal import savgol_filter
@@ -103,7 +105,7 @@ from rowers.emails import htmlstrip
from rowers import mytypes
from rowers.dataprep import (
from rowers.dataroutines import (
getsmallrowdata_db, updatecpdata_sql, update_c2id_sql,
#update_workout_field_sql,
update_agegroup_db, update_strokedata,
@@ -297,6 +299,42 @@ def summaryfromsplitdata(splitdata, data, filename, sep='|', workouttype='rower'
return sums, sa, results
@app.task
def instroke_static(w, metric, debug=False, **kwargs):
f1 = w.csvfilename[6:-4]
rowdata = rdata(csvfile=w.csvfilename)
timestr = strftime("%Y%m%d-%H%M%S")
imagename = f1+timestr+'.png'
fullpathimagename = 'static/plots/'+imagename
r = w.user
fig1 = rowdata.get_plot_instroke(metric)
canvas = FigureCanvas(fig1)
canvas.print_figure('static/plots/'+imagename)
plt.close(fig1)
fig1.clf()
try:
width, height = Image.open(fullpathimagename).size
except:
width = 1200
height = 600
imgs = GraphImage.objects.filter(workout=w)
if imgs.count() < 7:
i = GraphImage(workout=w,
creationdatetime=timezone.now(),
filename=fullpathimagename,
width=width, height=height)
i.save()
else:
return 0
return 1
@app.task
def handle_request_post(url, data, debug=False, **kwargs): # pragma: no cover

View File

@@ -9,7 +9,7 @@
<ul class="main-content">
<li class="grid_4">
{% if user.rower.rowerplan == 'basic' %}
<p>
This is a preview of the page with advanced functionality for Pro users.
See <a href="/rowers/about/">the About page</a> for more information
@@ -32,7 +32,7 @@
<td>
<a href="/rowers/workout/{{ workout.id|encode }}/">https://rowsandall.com/rowers/workout/{{ workout.id|encode }}</a>
<td>
</table>
</li>
@@ -42,14 +42,18 @@
{% if instrokemetrics %}
{% for metric in instrokemetrics %}
<p>
<a class="button blue small" href="/rowers/workout/{{ workout.id|encode }}/instroke/{{ metric }}/">{{ metric }}</a>
<a href="/rowers/workout/{{ workout.id|encode }}/instroke/{{ metric }}/">{{ metric }}</a>
</p>
{% endfor %}
<p>
<a href="/rowers/workout/{{ workout.id|encode }}/instroke/interactive/">NEW: Dynamic</a>
</p>
{% else %}
<p>Unfortunately, this workout doesn't have any in stroke metrics</p>
{% endif %}
</li>
</ul>

View File

@@ -0,0 +1,42 @@
{% extends "newbase.html" %}
{% load static %}
{% load rowerfilters %}
{% block title %}Advanced Features {% endblock %}
{% block main %}
{{ js_res | safe }}
{{ css_res| safe }}
<script src="https://cdn.pydata.org/bokeh/release/bokeh-2.2.3.min.js"></script>
<script src="https://cdn.pydata.org/bokeh/release/bokeh-widgets-2.2.3.min.js"></script>
<script async="true" type="text/javascript">
Bokeh.set_log_level("info");
</script>
{{ the_script |safe }}
<h1>In Stroke Metrics</h1>
<ul class="main-content">
<li class="grid_4">
<div id="theplot" class="flexplot">
{{ the_div|safe }}
</div>
</li>
<li class="grid_4">
<form action="" method="post">
{% csrf_token %}
<table>
{{ form.as_table }}
</table>
<input type="submit" value="Submit">
</form>
</li>
</ul>
{% endblock %}
{% block sidebar %}
{% include 'menu_workout.html' %}
{% endblock %}

View File

@@ -249,6 +249,8 @@ urlpatterns = [
path('404/', TemplateView.as_view(template_name='404.html'), name='404'),
path('400/', TemplateView.as_view(template_name='400.html'), name='400'),
path('403/', TemplateView.as_view(template_name='403.html'), name='403'),
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/instroke/interactive/$',
views.instroke_chart_interactive, name='instroke_chart_interactive'),
re_path(r'^exportallworkouts/?/$', views.workouts_summaries_email_view,
name='workouts_summaries_email_view'),
path('failedjobs/', views.failed_queue_view, name='failed_queue_view'),

View File

@@ -110,7 +110,7 @@ from rowers.forms import (
VideoAnalysisMetricsForm, SurveyForm, HistorySelectForm,
StravaChartForm, FitnessFitForm, PerformanceManagerForm,
TrainingPlanBillingForm, InstantPlanSelectForm,
TrainingZonesForm,
TrainingZonesForm, InstrokeForm
)
from django.urls import reverse, reverse_lazy
@@ -239,6 +239,7 @@ from rowers.rows import handle_uploaded_file, handle_uploaded_image
from rowers.plannedsessions import *
from rowers.tasks import handle_makeplot, handle_otwsetpower, handle_sendemailtcx, handle_sendemailcsv
from rowers.tasks import (
instroke_static,
fetch_rojabo_session,
handle_sendemail_unrecognized, handle_sendemailnewcomment,
handle_request_post,

View File

@@ -2904,36 +2904,8 @@ def instroke_chart(request, id=0, metric=''): # pragma: no cover
instrokemetrics = rowdata.get_instroke_columns()
if metric in instrokemetrics:
f1 = w.csvfilename[6:-4]
timestr = strftime("%Y%m%d-%H%M%S")
imagename = f1+timestr+'.png'
fullpathimagename = 'static/plots/'+imagename
u = w.user.user
r = getrower(u)
fig1 = rowdata.get_plot_instroke(metric)
canvas = FigureCanvas(fig1)
canvas.print_figure('static/plots/'+imagename)
plt.close(fig1)
fig1.clf()
gc.collect()
try:
width, height = Image.open(fullpathimagename).size
except:
width = 1200
height = 600
imgs = GraphImage.objects.filter(workout=w)
if imgs.count() < 7:
i = GraphImage(workout=w,
creationdatetime=timezone.now(),
filename=fullpathimagename,
width=width, height=height)
i.save()
else:
messages.error(
request, 'You have reached the maximum number of static images for this workout. Delete an image first')
job = myqueue(queuelow,
instroke_static,w, metric)
r = getrower(request.user)
url = reverse(r.defaultlandingpage,
@@ -2943,9 +2915,62 @@ def instroke_chart(request, id=0, metric=''): # pragma: no cover
return HttpResponseRedirect(url)
@permission_required('workout.change_workout', fn=get_workout_by_opaqueid, raise_exception=True)
def instroke_chart_interactive(request, id=0): # pragma: no cover
w = get_workoutuser(id, request)
rowdata = rrdata(csvfile=w.csvfilename)
instrokemetrics = rowdata.get_instroke_columns()
form = InstrokeForm(choices=instrokemetrics)
script = ''
div = ''
if request.method == 'POST':
form = InstrokeForm(request.POST,choices=instrokemetrics)
if form.is_valid():
metric = form.cleaned_data['metric']
spm_min = form.cleaned_data['spm_min']
spm_max = form.cleaned_data['spm_max']
data = rowdata.get_instroke_data(metric,
spm_min=spm_min,
spm_max=spm_max)
script, div = instroke_interactive_chart(data, metric, w,
spm_min,
spm_max)
breadcrumbs = [
{
'url': '/rowers/list-workouts/',
'name': 'Workouts'
},
{
'url': get_workout_default_page(request, id),
'name': w.name
},
{
'url': reverse('instroke_view', kwargs={'id': id}),
'name': 'In-Stroke Metrics'
}
]
return render(request,
'instroke_interactive.html',
{
'workout': w,
'rower': w.user,
'active': 'nav-workouts',
'breadcrumbs': breadcrumbs,
'teams': get_my_teams(request.user),
'instrokemetrics': instrokemetrics,
'form':form,
'the_script': script,
'the_div': div,
})
# erase column
@permission_required('workout.change_workout', fn=get_workout_by_opaqueid, raise_exception=True)
def workout_erase_column_view(request, id=0, column=''):
r = getrower(request.user)