diff --git a/rowers/#tasks.py# b/rowers/#tasks.py# new file mode 100644 index 00000000..f37fe6ab --- /dev/null +++ b/rowers/#tasks.py# @@ -0,0 +1,495 @@ +from celery import Celery,app +import os +import time +import gc +import gzip +import shutil +import numpy as np + +import rowingdata +from rowingdata import main as rmain +from rowingdata import rowingdata as rdata +import rowingdata + +from matplotlib.backends.backend_agg import FigureCanvas +#from matplotlib.backends.backend_cairo import FigureCanvasCairo as FigureCanvas +import matplotlib.pyplot as plt +from matplotlib import figure + +import stravalib + +from utils import serialize_list,deserialize_list + +from rowers.dataprepnodjango import update_strokedata + + +from django.core.mail import send_mail, BadHeaderError,EmailMessage + +# testing task +@app.task +def add(x, y): + return x + y + +# send email to me when an unrecognized file is uploaded +@app.task +def handle_sendemail_unrecognized(unrecognizedfile,useremail): + + # send email with attachment + fullemail = 'roosendaalsander@gmail.com' + subject = "Unrecognized file from Rowsandall.com" + message = "Dear Sander,\n\n" + message += "Please find attached a file that someone tried to upload to rowsandall.com. The file was not recognized as a valid file type.\n\n" + message += "User Email "+useremail+"\n\n" + message += "Best Regards, the Rowsandall Team" + + email = EmailMessage(subject, message, + 'Rowsandall ', + [fullemail]) + + + email.attach_file(unrecognizedfile) + + res = email.send() + + # remove tcx file + os.remove(unrecognizedfile) + return 1 + + +# Send email with TCX attachment +@app.task +def handle_sendemailtcx(first_name,last_name,email,tcxfile): + + # send email with attachment + fullemail = first_name + " " + last_name + " " + "<" + email + ">" + subject = "File from Rowsandall.com" + message = "Dear "+first_name+",\n\n" + message += "Please find attached the requested file for your workout.\n\n" + message += "Best Regards, the Rowsandall Team" + + email = EmailMessage(subject, message, + 'Rowsandall ', + [fullemail]) + + + email.attach_file(tcxfile) + + res = email.send() + + # remove tcx file + os.remove(tcxfile) + return 1 + +# Send email with CSV attachment +@app.task +def handle_sendemailcsv(first_name,last_name,email,csvfile): + + # send email with attachment + fullemail = first_name + " " + last_name + " " + "<" + email + ">" + subject = "File from Rowsandall.com" + message = "Dear "+first_name+",\n\n" + message += "Please find attached the requested file for your workout.\n\n" + message += "Best Regards, the Rowsandall Team" + + email = EmailMessage(subject, message, + 'Rowsandall ', + [fullemail]) + + + if os.path.isfile(csvfile): + email.attach_file(csvfile) + else: + csvfile2 = csvfile + with gzip.open(csvfile+'.gz','rb') as f_in, open(csvfile2,'wb') as f_out: + shutil.copyfileobj(f_in, f_out) + + email.attach_file(csvfile2) + os.remove(csvfile2) + + res = email.send() + + return 1 + +# Calculate wind and stream corrections for OTW rowing +@app.task +def handle_otwsetpower(f1,boattype,weightvalue, + first_name,last_name,email,workoutid, + debug=False): + try: + rowdata = rdata(f1) + except IOError: + try: + rowdata = rdata(f1+'.csv') + except IOError: + rowdata = rdata(f1+'.gz') + + weightvalue = float(weightvalue) + + # do something with boat type + boatfile = { + '1x':'static/rigging/1x.txt', + '2x':'static/rigging/2x.txt', + '2-':'static/rigging/2-.txt', + '4x':'static/rigging/4x.txt', + '4-':'static/rigging/4-.txt', + '8+':'static/rigging/8+.txt', + } + try: + rg = rowingdata.getrigging(boatfile[boattype]) + except KeyError: + rg = rowingdata.getrigging('static/rigging/1x.txt') + + # do calculation, but do not overwrite NK Empower Power data + powermeasured = False + try: + w = rowdata.df['wash'] + powermeasured = True + except KeyError: + pass + + rowdata.otw_setpower_silent(skiprows=5,mc=weightvalue,rg=rg, + powermeasured=powermeasured) + + # save data + rowdata.write_csv(f1) + update_strokedata(workoutid,rowdata.df,debug=debug) + + # send email + fullemail = first_name + " " + last_name + " " + "<" + email + ">" + subject = "Your Rowsandall OTW calculations are ready" + message = "Dear "+first_name+",\n\n" + message += "Your Rowsandall OTW calculations are ready.\n" + message += "Thank you for using rowsandall.com.\n\n" + message += "Rowsandall OTW calculations have not been fully implemented yet.\n" + message += "We are now running an experimental version for debugging purposes. \n" + message += "Your wind/stream corrected plot is available here: http://rowsandall.com/rowers/workout/" + message += str(workoutid) + message +="/interactiveotwplot\n\n" + message += "Please report any bugs/inconsistencies/unexpected results at rowsandall.slack.com or by reply to this email.\n\n" + message += "Best Regards, The Rowsandall Physics Department." + + send_mail(subject, message, + 'Rowsandall Physics Department ', + [fullemail]) + + return 1 + +# This function generates all the static (PNG image) plots +@app.task +def handle_makeplot(f1,f2,t,hrdata,plotnr,imagename): + + hrmax = hrdata['hrmax'] + hrut2 = hrdata['hrut2'] + hrut1 = hrdata['hrut1'] + hrat = hrdata['hrat'] + hrtr = hrdata['hrtr'] + hran = hrdata['hran'] + ftp = hrdata['ftp'] + powerzones = deserialize_list(hrdata['powerzones']) + powerperc = np.array(deserialize_list(hrdata['powerperc'])).astype(int) + + rr = rowingdata.rower(hrmax=hrmax,hrut2=hrut2, + hrut1=hrut1,hrat=hrat, + hrtr=hrtr,hran=hran, + ftp=ftp,powerperc=powerperc, + powerzones=powerzones) + try: + row = rdata(f2,rower=rr) + except IOError: + row = rdata(f2+'.gz',rower=rr) + + + haspower = row.df[' Power (watts)'].mean() > 50 + + nr_rows = len(row.df) + if (plotnr in [1,2,4,5,8,11,9,12]) and (nr_rows > 1200): + bin = int(nr_rows/1200.) + df = row.df.groupby(lambda x:x/bin).mean() + row.df = df + nr_rows = len(row.df) + if (plotnr==1): + fig1 = row.get_timeplot_erg(t) + elif (plotnr==2): + fig1 = row.get_metersplot_erg(t) + elif (plotnr==3): + fig1 = row.get_piechart(t) + elif (plotnr==4): + if haspower: + fig1 = row.get_timeplot_otwempower(t) + else: + fig1 = row.get_timeplot_otw(t) + elif (plotnr==5): + if haspower: + fig1 = row.get_metersplot_otwempower(t) + else: + fig1 = row.get_metersplot_otw(t) + elif (plotnr==6): + fig1 = row.get_piechart(t) + elif (plotnr==7) or (plotnr==10): + fig1 = row.get_metersplot_erg2(t) + elif (plotnr==8) or (plotnr==11): + fig1 = row.get_timeplot_erg2(t) + elif (plotnr==9) or (plotnr==12): + fig1 = row.get_time_otwpower(t) + elif (plotnr==13) or (plotnr==16): + fig1 = row.get_power_piechart(t) + + canvas = FigureCanvas(fig1) + + # plt.savefig('static/plots/'+imagename,format='png') + canvas.print_figure('static/plots/'+imagename) + # plt.imsave(fname='static/plots/'+imagename) + plt.close(fig1) + fig1.clf() + gc.collect() + return imagename + +# Team related remote tasks + +@app.task +def handle_sendemail_invite(email,name,code,teamname,manager): + fullemail = name+' <'+email+'>' + subject = 'Invitation to join team '+teamname + message = 'Dear '+name+',\n\n' + message += manager+' is inviting you to join his team '+teamname + message += ' on rowsandall.com\n\n' + message += 'By accepting the invite, you will have access to your' + message += " team's workouts on rowsandall.com and your workouts will " + message += " be visible to " + message += "the members of the team.\n\n" + message += 'If you already have an account on rowsandall.com, you can login to the site and you will find the invitation here on the Teams page:\n' + message += ' https://rowsandall.com/rowers/me/teams \n\n' + message += 'You can also click the direct link: \n' + message += 'https://rowsandall.com/rowers/me/invitation/'+code+' \n\n' + message += 'If you are not yet registered to rowsandall.com, ' + message += 'you can register for free at https://rowsandall.com/rowers/register\n' + message += 'After you set up your account, you can use the direct link: ' + message += 'https://rowsandall.com/rowers/me/invitation/'+code+' \n\n' + + message += 'You can also manually accept your team membership with the code.\n' + message += 'You will need to do this if you registered under a different email address than this one.\n' + message += 'Code: '+code+'\n' + message += 'Link to manually accept your team membership: ' + message += 'https://rowsandall.com/rowers/me/invitation\n\n' + message += "Best Regards, the Rowsandall Team" + + email = EmailMessage(subject, message, + 'Rowsandall ', + [fullemail]) + + + res = email.send() + + return 1 + +@app.task +def handle_sendemailnewresponse(first_name,last_name, + email, + commenter_first_name, + commenter_last_name, + comment, + workoutname,workoutid,commentid): + fullemail = first_name+' '+last_name+' <'+email+'>' + subject = 'New comment on workout '+workoutname + message = 'Dear '+first_name+',\n\n' + message += commenter_first_name+' '+commenter_last_name + message += ' has written a new comment on the workout ' + message += workoutname+'\n\n' + message += comment + message += '\n\n' + message += 'You can read the comment here:\n' + message += 'https://rowsandall.com/rowers/workout/'+str(workoutid)+'/comment' + message += '\n\n' + message += 'You are receiving this email because you are subscribed ' + message += 'to comments on this workout. To unsubscribe, follow this link:\n' + message += 'https://rowsandall.com/rowers/workout/'+str(workoutid)+'/unsubscribe' + + email = EmailMessage(subject, message, + 'Rowsandall ', + [fullemail]) + + + res = email.send() + + return 1 + +@app.task +def handle_sendemailnewcomment(first_name, + last_name, + email, + commenter_first_name, + commenter_last_name, + comment,workoutname, + workoutid): + fullemail = first_name+' '+last_name+' <'+email+'>' + subject = 'New comment on workout '+workoutname + message = 'Dear '+first_name+',\n\n' + message += commenter_first_name+' '+commenter_last_name + message += ' has written a new comment on your workout ' + message += workoutname+'\n\n' + message += comment + message += '\n\n' + message += 'You can read the comment here:\n' + message += 'https://rowsandall.com/rowers/workout/'+str(workoutid)+'/comment' + + email = EmailMessage(subject, message, + 'Rowsandall ', + [fullemail]) + + + res = email.send() + + return 1 + + +@app.task +def handle_sendemail_request(email,name,code,teamname,requestor,id): + fullemail = name+' <'+email+'>' + subject = 'Request to join team '+teamname + message = 'Dear '+name+',\n\n' + message += requestor+' is requesting admission to your team '+teamname + message += ' on rowsandall.com\n\n' + message += 'Click the direct link to accept: \n' + message += 'https://rowsandall.com/rowers/me/request/'+code+' \n\n' + message += 'Click the following link to reject the request: \n' + message += 'https://rowsandall.com/rowers/me/request/'+str(id)+' \n\n' + message += 'You can find all pending requests on your team management page:\n' + message += 'https://rowsandall.com/rowers/me/teams\n\n' + message += "Best Regards, the Rowsandall Team" + + email = EmailMessage(subject, message, + 'Rowsandall ', + [fullemail]) + + + res = email.send() + + return 1 + +@app.task +def handle_sendemail_request_accept(email,name,teamname,managername): + fullemail = name+' <'+email+'>' + subject = 'Welcome to '+teamname + message = 'Dear '+name+',\n\n' + message += managername + message += ' has accepted your request to be part of the team ' + message += teamname + message += '\n\n' + message += "Best Regards, the Rowsandall Team" + + email = EmailMessage(subject, message, + 'Rowsandall ', + [fullemail]) + + + res = email.send() + + return 1 + +@app.task +def handle_sendemail_request_reject(email,name,teamname,managername): + fullemail = name+' <'+email+'>' + subject = 'Your application to '+teamname+' was rejected' + message = 'Dear '+name+',\n\n' + message += 'Unfortunately, ' + message += managername + message += ' has rejected your request to be part of the team ' + message += teamname + message += '\n\n' + message += "Best Regards, the Rowsandall Team" + + email = EmailMessage(subject, message, + 'Rowsandall ', + [fullemail]) + + + res = email.send() + + return 1 + +@app.task +def handle_sendemail_member_dropped(email,name,teamname,managername): + fullemail = name+' <'+email+'>' + subject = 'You were removed from '+teamname + message = 'Dear '+name+',\n\n' + message += 'Unfortunately, ' + message += managername + message += ' has removed you from the team ' + message += teamname + message += '\n\n' + message += "Best Regards, the Rowsandall Team" + + email = EmailMessage(subject, message, + 'Rowsandall ', + [fullemail]) + + + res = email.send() + + return 1 + +@app.task +def handle_sendemail_team_removed(email,name,teamname,managername): + fullemail = name+' <'+email+'>' + subject = 'Team '+teamname+' was deleted' + message = 'Dear '+name+',\n\n' + message += managername + message += ' has decided to delete the team ' + message += teamname + message += '\n\n' + message += 'The '+teamname+' tag has been removed from all your ' + message += 'workouts on rowsandall.com.\n\n' + message += "Best Regards, the Rowsandall Team" + + email = EmailMessage(subject, message, + 'Rowsandall ', + [fullemail]) + + + res = email.send() + + return 1 + +@app.task +def handle_sendemail_invite_reject(email,name,teamname,managername): + fullemail = managername+' <'+email+'>' + subject = 'Your invitation to '+name+' was rejected' + message = 'Dear '+managername+',\n\n' + message += 'Unfortunately, ' + message += name + message += ' has rejected your invitation to be part of the team ' + message += teamname + message += '\n\n' + message += "Best Regards, the Rowsandall Team" + + email = EmailMessage(subject, message, + 'Rowsandall ', + [fullemail]) + + + res = email.send() + + return 1 + +@app.task +def handle_sendemail_invite_accept(email,name,teamname,managername): + fullemail = managername+' <'+email+'>' + subject = 'Your invitation to '+name+' was accepted' + message = 'Dear '+managername+',\n\n' + message += name+' has accepted your invitation to be part of the team '+teamname+'\n\n' + message += "Best Regards, the Rowsandall Team" + + email = EmailMessage(subject, message, + 'Rowsandall ', + [fullemail]) + + + res = email.send() + + return 1 + + + +# Another simple task for debugging purposes +def add2(x,y): + return x+y diff --git a/rowers/.#interactiveplots.py b/rowers/.#interactiveplots.py deleted file mode 100644 index ef817ef8..00000000 --- a/rowers/.#interactiveplots.py +++ /dev/null @@ -1 +0,0 @@ -E408191@CZ27LT9RCGN72.15972:1488820163 \ No newline at end of file diff --git a/rowers/.#tasks.py b/rowers/.#tasks.py new file mode 100644 index 00000000..d6002777 --- /dev/null +++ b/rowers/.#tasks.py @@ -0,0 +1 @@ +E408191@CZ27LT9RCGN72.13016:1488979920 \ No newline at end of file diff --git a/rowers/dataprep.py b/rowers/dataprep.py index d15df5f0..6e3d177b 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -12,7 +12,8 @@ from rowingdata import get_file_type,get_empower_rigging from pandas import DataFrame,Series from pytz import timezone as tz,utc - +from django.utils import timezone +from time import strftime,strptime,mktime,time,daylight from django.utils.timezone import get_current_timezone thetimezone = get_current_timezone() from rowingdata import ( @@ -80,7 +81,8 @@ columndict = { 'finish':'finish', 'peakforceangle':'peakforceangle', 'wash':'wash', - 'slip':'wash', + 'slip':'wash', + 'workoutstate':' WorkoutState', } from scipy.signal import savgol_filter @@ -621,6 +623,62 @@ def new_workout_from_file(r,f2, return (id,message,f2) +# Create new workout from data frame and store it in the database +# This routine should be used everywhere in views.py and mailprocessing.py +# Currently there is code duplication +def new_workout_from_df(r,df, + title='New Workout', + parent=None): + + message = None + + summary = '' + if parent: + oarlength = parent.oarlength + inboard = parent.inboard + workouttype = parent.workouttype + notes=parent.notes + summary=parent.summary + makeprivate=parent.privacy + startdatetime=parent.startdatetime + else: + oarlength = 2.89 + inboard = 0.88 + workouttype = 'rower' + notes='' + summary='' + makeprivate=False + startdatetime = timezone.now() + + timestr = strftime("%Y%m%d-%H%M%S") + + csvfilename ='media/Fusion_'+timestr+'.csv' + + df.rename(columns = columndict,inplace=True) + starttimeunix = mktime(startdatetime.utctimetuple()) + df[' ElapsedTime (sec)'] = df['TimeStamp (sec)'] + df['TimeStamp (sec)'] = df['TimeStamp (sec)']+starttimeunix + + row = rrdata(df=df) + row.write_csv(csvfilename,gzip=True) + + #res = df.to_csv(csvfilename+'.gz',index_label='index', + # compression='gzip') + + id,message = save_workout_database(csvfilename,r, + workouttype=workouttype, + title=title, + notes=notes, + oarlength=oarlength, + inboard=inboard, + makeprivate=makeprivate, + dosmooth=False) + + + return (id,message) + + + # Compare the data from the CSV file and the database # Currently only calculates number of strokes. To be expanded with # more elaborate testing if needed @@ -696,10 +754,10 @@ def repair_data(verbose=False): # A wrapper around the rowingdata class, with some error catching def rdata(file,rower=rrower()): try: - res = rrdata(file,rower=rower) + res = rrdata(csvfile=file,rower=rower) except IOError,IndexError: try: - res = rrdata(file+'.gz',rower=rower) + res = rrdata(csvfile=file+'.gz',rower=rower) except IOError,IndexError: res = 0 @@ -900,11 +958,21 @@ def smalldataprep(therows,xparam,yparam1,yparam2): # data fusion def datafusion(id1,id2,columns,offset): - df1 = getrowdata_db(id=id1) + df1,w1 = getrowdata_db(id=id1) + df1 = df1.drop(['cumdist', + 'hr_ut2', + 'hr_ut1', + 'hr_at', + 'hr_tr', + 'hr_an', + 'hr_max',], + 1,errors='ignore') columns = ['time']+columns - df2 = getsmallrowdata_db(columns,ids=[id2]) + df2 = getsmallrowdata_db(columns,ids=[id2],doclean=False) - keep1 = set(df1.columns) + print df1['pace'].mean()/1000.,'mies' + + keep1 = {c:c for c in set(df1.columns)} for c in columns: keep1.pop(c) @@ -913,10 +981,14 @@ def datafusion(id1,id2,columns,offset): df1 = df1.drop(c,1,errors='ignore') df = pd.concat([df1,df2],ignore_index=True) - df = df.sort_value(['time']) - df.interpolate(method='linear',axis=0,limit_direction='both') + df = df.sort_values(['time']) + df = df.interpolate(method='linear',axis=0,limit_direction='both') df.fillna(method='bfill',inplace=True) - + + df['time'] = df['time']/1000. + df['pace'] = df['pace']/1000. + print df['pace'].mean(),'noot' + return df # This is the main routine. @@ -933,6 +1005,7 @@ def dataprep(rowdatadf,id=0,bands=True,barchart=True,otwpower=True, rowdatadf.loc[row_index,' Stroke500mPace (sec/500m)'] = 3000. p = rowdatadf.ix[:,' Stroke500mPace (sec/500m)'] + print p.mean(),'aap' hr = rowdatadf.ix[:,' HRCur (bpm)'] spm = rowdatadf.ix[:,' Cadence (stokes/min)'] cumdist = rowdatadf.ix[:,'cum_dist'] diff --git a/rowers/views.py b/rowers/views.py index 66defa93..ab49fc7a 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -4992,12 +4992,37 @@ def workout_fusion_view(request,id1=0,id2=1): try: w1 = Workout.objects.get(id=id1) w2 = Workout.objects.get(id=id2) + r = w1.user if (checkworkoutuser(request.user,w1)==False) or \ (checkworkoutuser(request.user,w2)==False): raise PermissionDenied("You are not allowed to use these workouts") except Workout.DoesNotExist: raise Http404("One of the workouts doesn't exist") + if request.method == 'POST': + form = FusionMetricChoiceForm(request.POST) + if form.is_valid(): + cd = form.cleaned_data + columns = cd['columns'] + df = dataprep.datafusion(id1,id2,columns,0) + idnew,message = dataprep.new_workout_from_df(r,df, + title='Fused data', + parent=w1) + url = reverse(workout_edit_view, + kwargs={ + 'message':message, + 'id':idnew, + }) + + return HttpResponseRedirect(url) + else: + return render(request, 'fusion.html', + {'form':form, + 'workout1':w1, + 'workout2':w2, + }) + + form = FusionMetricChoiceForm() return render(request, 'fusion.html',