Private
Public Access
1
0

MVP in-stroke analysis comparison

This commit is contained in:
Sander Roosendaal
2022-10-09 18:08:25 +02:00
parent dab3cd1e63
commit afcb6b56f8
6 changed files with 126 additions and 5 deletions

View File

@@ -10,7 +10,7 @@ from django.contrib.admin.widgets import FilteredSelectMultiple
from rowers.models import ( from rowers.models import (
Workout, Rower, Team, PlannedSession, GeoCourse, Workout, Rower, Team, PlannedSession, GeoCourse,
VirtualRace, VirtualRaceResult, IndoorVirtualRaceResult, VirtualRace, VirtualRaceResult, IndoorVirtualRaceResult,
PaidPlan PaidPlan, InStrokeAnalysis
) )
from rowers.rows import validate_file_extension, must_be_csv, validate_image_extension, validate_kml from rowers.rows import validate_file_extension, must_be_csv, validate_image_extension, validate_kml
from django.contrib.auth.forms import UserCreationForm from django.contrib.auth.forms import UserCreationForm
@@ -1250,6 +1250,13 @@ class WorkoutSingleSelectForm(forms.Form):
self.fields['workout'].queryset = workouts self.fields['workout'].queryset = workouts
class InStrokeMultipleCompareForm(forms.Form):
analyses = forms.ModelMultipleChoiceField(
queryset=InStrokeAnalysis.objects.all(),
widget=forms.CheckboxSelectMultiple()
)
class WorkoutMultipleCompareForm(forms.Form): class WorkoutMultipleCompareForm(forms.Form):
workouts = forms.ModelMultipleChoiceField( workouts = forms.ModelMultipleChoiceField(
queryset=Workout.objects.filter(), queryset=Workout.objects.filter(),

View File

@@ -5,7 +5,7 @@ from rowers.metrics import rowingmetrics, metricsdicts
from scipy.spatial import ConvexHull, Delaunay from scipy.spatial import ConvexHull, Delaunay
from scipy.stats import linregress, percentileofscore from scipy.stats import linregress, percentileofscore
from pytz import timezone as tz, utc from pytz import timezone as tz, utc
from rowers.models import course_spline, VirtualRaceResult from rowers.models import course_spline, VirtualRaceResult, InStrokeAnalysis
from bokeh.palettes import Category20c, Category10 from bokeh.palettes import Category20c, Category10
from bokeh.layouts import layout, widgetbox from bokeh.layouts import layout, widgetbox
from bokeh.resources import CDN, INLINE from bokeh.resources import CDN, INLINE
@@ -4078,6 +4078,81 @@ def interactive_streamchart(id=0, promember=0):
return [script, div] return [script, div]
def instroke_multi_interactive_chart(selected):
df_plot = pd.DataFrame()
ids = [analysis.id for analysis in selected]
for analysis in selected:
#start_second, end_second, spm_min, spm_max, name
activeminutesmin = int(analysis.start_second/60.)
activeminutesmax = int(analysis.end_second/60.)
rowdata = rrdata(csvfile=analysis.workout.csvfilename)
data = rowdata.get_instroke_data(
analysis.metric,
spm_min=analysis.spm_min,
spm_max=analysis.spm_max,
activeminutesmin=activeminutesmin,
activeminutesmax=activeminutesmax,
)
mean_vals = data.mean()
xvals = np.arange(len(mean_vals))
xname = 'x_'+str(analysis.id)
yname = 'y_'+str(analysis.id)
df_plot[xname] = xvals
df_plot[yname] = mean_vals
source = ColumnDataSource(
df_plot
)
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'
# 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",
)
colors = itertools.cycle(palette)
try:
items = itertools.izip(ids, colors)
except AttributeError:
items = zip(ids, colors)
for id, color in items:
xname = 'x_'+str(id)
yname = 'y_'+str(id)
analysis = InStrokeAnalysis.objects.get(id=id)
plot.line(xname,yname,source=source,legend_label=analysis.name,
line_width=2, color=color)
script, div = components(plot)
return (script, div)
def instroke_interactive_chart(df,metric, workout, spm_min, spm_max, def instroke_interactive_chart(df,metric, workout, spm_min, spm_max,
activeminutesmin, activeminutesmax, activeminutesmin, activeminutesmax,
individual_curves, individual_curves,

View File

@@ -6,16 +6,37 @@
{% block main %} {% 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 Analysis for {{ rower.user.first_name }} {{ rower.user.last_name }}</h1> <h1>In-Stroke Analysis for {{ rower.user.first_name }} {{ rower.user.last_name }}</h1>
<form enctype="multipart/form-data" method="post">
<ul class="main-content"> <ul class="main-content">
{% if the_div %}
<li class="grid_4">
<div id="theplot" class="flexplot">
{{ the_div|safe }}
</div>
</li>
{% endif %}
{% if analyses %} {% if analyses %}
{% for analysis in analyses %} {% for analysis in analyses %}
<li class="grid_4 divlines" id="analysis_{{ analysis.id }}"> <li class="grid_4 divlines" id="analysis_{{ analysis.id }}">
{{ analysis.date }} {{ analysis.date }}
<div><h3>{{ analysis.name }}</h3></div> <div><h3>{{ analysis.name }}</h3></div>
<div class="analysiscontainer"> <div class="analysiscontainer">
<div class="workoutelement">
<input type="checkbox" name="analyses" value="{{ analysis.id }}" id="analyses_{{ analysis.id }}">
</div>
<div class="workoutelement"> <div class="workoutelement">
<a class="small" href="/rowers/workout/{{ analysis.workout.id|encode }}/instroke/interactive/{{ analysis.id }}/" <a class="small" href="/rowers/workout/{{ analysis.workout.id|encode }}/instroke/interactive/{{ analysis.id }}/"
title="Edit"> title="Edit">
@@ -57,6 +78,9 @@
</li> </li>
{% endif %} {% endif %}
</ul> </ul>
{% csrf_token %}
<input name='instroke_compare' type="submit" value="Compare Selected">
</form>

View File

@@ -1852,6 +1852,19 @@ def instrokeanalysis_view(request, userid=0):
analyses = InStrokeAnalysis.objects.filter(rower=r).order_by("-date","-id") analyses = InStrokeAnalysis.objects.filter(rower=r).order_by("-date","-id")
script = ""
div = ""
if request.method == 'POST':
form = InStrokeMultipleCompareForm(request.POST)
if form.is_valid():
cd = form.cleaned_data
selected = cd['analyses']
request.session['analyses'] = [a.id for a in selected]
# now should redirect to analysis
script, div = instroke_multi_interactive_chart(selected)
breadcrumbs = [ breadcrumbs = [
{ {
'url': '/rowers/analysis', 'url': '/rowers/analysis',
@@ -1868,6 +1881,8 @@ def instrokeanalysis_view(request, userid=0):
'breadcrumbs': breadcrumbs, 'breadcrumbs': breadcrumbs,
'analyses': analyses, 'analyses': analyses,
'rower': r, 'rower': r,
'the_script': script,
'the_div': div,
}) })

View File

@@ -111,7 +111,7 @@ from rowers.forms import (
VideoAnalysisMetricsForm, SurveyForm, HistorySelectForm, VideoAnalysisMetricsForm, SurveyForm, HistorySelectForm,
StravaChartForm, FitnessFitForm, PerformanceManagerForm, StravaChartForm, FitnessFitForm, PerformanceManagerForm,
TrainingPlanBillingForm, InstantPlanSelectForm, TrainingPlanBillingForm, InstantPlanSelectForm,
TrainingZonesForm, InstrokeForm TrainingZonesForm, InstrokeForm, InStrokeMultipleCompareForm
) )
from django.urls import reverse, reverse_lazy from django.urls import reverse, reverse_lazy

View File

@@ -397,7 +397,7 @@ th.rotate > div > span {
.analysiscontainer { .analysiscontainer {
display: grid; display: grid;
grid-template-columns: 50px repeat(auto-fit, minmax(calc((100% - 100px)/6), 1fr)); grid-template-columns: 50px repeat(auto-fit, minmax(calc((100% - 100px)/7), 1fr));
/* grid-template-columns: 50px repeat(auto-fit, minmax(100px, 1fr)) 50px; ????*/ /* grid-template-columns: 50px repeat(auto-fit, minmax(100px, 1fr)) 50px; ????*/
padding: 5px; padding: 5px;
margin: 0px; margin: 0px;