diff --git a/.gitignore b/.gitignore
index 82ce638e24652fda99b4442de0cf70d8660d4f49..d3c652fd439d8383ba3a20db17a1b4500514e910 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,4 +13,9 @@ config.py
 *.db
 /embody
 /app/static/lib
+.env
+/app/static/*.png
+/app/static/embody_images
+.vscode/
+documentation
 
diff --git a/app/__init__.py b/app/__init__.py
index 2fd3d82fefb5c8b25d7d0d93cae852662cfbc5c4..71c6ee7e0970ac6a6dea8fa8cde8bf4781bd880e 100644
--- a/app/__init__.py
+++ b/app/__init__.py
@@ -15,7 +15,7 @@ from flask_cors import CORS, cross_origin
 app = Flask(__name__)
 
 CORS(app)
-#CORS(app, resources={r"/*": {"cors_allowed_origins":"*"} } )
+# CORS(app, resources={r"/*": {"cors_allowed_origins":"*"} } )
 
 #app.config['BABEL_DEFAULT_LOCALE'] = 'fin'
 #app.config['BABEL_TRANSLATION_DIRECTORIES'] ='C:/Users/Timo/git/pet-rating/app/translations'
@@ -68,9 +68,6 @@ def get_locale():
 """
 
 
-# Run flask app with socketIO
-socketio = SocketIO()
-socketio.init_app(app)
 
 #mariabd mysql portti 3306 tarkista?
 
@@ -81,6 +78,11 @@ migrate = Migrate(app, db)
 login = LoginManager(app)
 login.login_view = 'login'
 
+# Run flask app with socketIO
+socketio = SocketIO(app, cors_allowed_origins="*")
+# socketio = SocketIO()
+socketio.init_app(app)
+
 # Register blueprints
 from .task.views import task_blueprint
 from .experiment.views import experiment_blueprint
diff --git a/app/experiment/templates/add_embody.html b/app/experiment/templates/add_embody.html
index 535fb830e4340b5cacaa79a2e660ec0002b141a4..b7bdc332f86ac5db4d4857a3bc31e90c7ba6e718 100644
--- a/app/experiment/templates/add_embody.html
+++ b/app/experiment/templates/add_embody.html
@@ -3,8 +3,7 @@
   
 <h1 class="container mt-5 display-4 text-center"><br>Add new embody picture:</h1>
 <br>
-<p class="lead">
-Upload new embody image... instructions here for admins..
+<p class="lead"> Upload new embody image. Submit without choosing file if you want to use default embody picture.
 </p>
 
 {% from "_formhelpers.html" import render_field %}
@@ -12,8 +11,8 @@ Upload new embody image... instructions here for admins..
 <form method="post" enctype="multipart/form-data">
 
     <div class="form-group">
-        <label for="Background questions">image question/explanation:</label>
-        <textarea class="form-control" rows="5" id="embody_picture_text" name="question"></textarea>
+        <label for="Background questions">Image question/explanation:</label>
+        <textarea class="form-control" rows="5" id="embody_picture_text" name="question">Color the regions whose activity you feel increasing or getting stronger</textarea>
     </div>
 
     <div class="custom-file">
@@ -23,7 +22,7 @@ Upload new embody image... instructions here for admins..
 
     <hr>
 
-    <button type="submit" class="btn btn-primary submit-file" disabled>Submit</button>
+    <button type="submit" class="btn btn-primary">Submit</button>
     <a class="btn btn-primary" href="{{ request.referrer }}" role="button">Cancel</a>
 </form>
 
diff --git a/app/experiment/templates/experiment_statistics.html b/app/experiment/templates/experiment_statistics.html
index 817470eafe2cfc7e485c63e9c1c60db30827c24d..76b2e9edb2a6f0a0afcc6a6cbf341090860e6415 100644
--- a/app/experiment/templates/experiment_statistics.html
+++ b/app/experiment/templates/experiment_statistics.html
@@ -36,7 +36,27 @@
     <tr>
       <td>Number of finished ratings:</td>
       <td>{{ finished_ratings }}
-      <a class="btn btn-primary btn-info float-right" href="{{ url_for('download_csv', exp_id=exp.idexperiment) }}" role="button">Export results (csv)</a>
+
+
+        <button data-value="{{ exp.idexperiment }}" class="btn btn-primary float-right get-csv-results">
+
+          export results
+
+        </button>
+
+        <div id="export-link-container" class="hidden">
+          <a id="export-link" class="float-right"
+            href="{{ url_for('experiment.download_csv', exp_id=exp.idexperiment) }}" role="button"></a>
+          <p id="export-error"></p>
+        </div>
+
+
+        <div class="progress hidden">
+          <div id="export-results-bar" class="progress-bar progress-bar-striped progress-bar-animated"
+            role="progressbar" aria-valuenow="50" aria-valuemin="0" aria-valuemax="100" style="width: 0%">
+          </div>
+        </div>
+
       </td>
     </tr>
   </tbody>
@@ -49,21 +69,21 @@
 <table class="table">
   <thead>
     <tr>
-    <th scope="col" nowrap>Question ID:</td>
-    <th scope="col" nowrap>Question:</th>
-    <th scope="col" nowrap>Left scale</th>
-    <th scope="col" nowrap>Right scale</th>
+      <th scope="col" nowrap>Question ID:</td>
+      <th scope="col" nowrap>Question:</th>
+      <th scope="col" nowrap>Left scale</th>
+      <th scope="col" nowrap>Right scale</th>
     </tr>
   </thead>
   <tbody>
-    {% for q in question_headers %}    
+    {% for q in question_headers %}
     <tr>
-    <td>{{ q.idquestion }}</td>
-    <td>{{ q.question }}</td>
-    <td>{{ q.left }}</td>
-    <td>{{ q.right }}</td>
+      <td>{{ q.idquestion }}</td>
+      <td>{{ q.question }}</td>
+      <td>{{ q.left }}</td>
+      <td>{{ q.right }}</td>
     </tr>
-    {% endfor %}    
+    {% endfor %}
   </tbody>
 </table>
 
@@ -74,29 +94,29 @@
 
 <table class="table">
   <thead>
-      
+
     <tr>
-     <th scope="col" nowrap>Participant ID:</th>
-     {% for page in pages_and_questions %}
-     
-            {% for p in pages_and_questions[page] %}
-             <th scope="col" nowrap>{{ p[0]}}/{{ p[1]}}</th>
-            {% endfor %}
-
-     {% endfor %}
+      <th scope="col" nowrap>Participant ID:</th>
+      {% for page in pages_and_questions %}
+
+      {% for p in pages_and_questions[page] %}
+      <th scope="col" nowrap>{{ p[0]}}/{{ p[1]}}</th>
+      {% endfor %}
+
+      {% endfor %}
     </tr>
   </thead>
   <tbody>
-    {% for participant in participants_and_answers %}    
+    {% for participant in participants_and_answers %}
     <tr>
-	{% if participant == 'mean' %}
-        <td><b>{{ participant }}</b></td>
-	{% else  %}
-        <td>{{ participant }}</td>
-	{% endif  %}
-        {% for answer in participants_and_answers[participant] %}
-            <td>{{ answer }}</td>
-        {% endfor %}
+      {% if participant == 'mean' %}
+      <td><b>{{ participant }}</b></td>
+      {% else  %}
+      <td>{{ participant }}</td>
+      {% endif  %}
+      {% for answer in participants_and_answers[participant] %}
+      <td>{{ answer }}</td>
+      {% endfor %}
     </tr>
     {% endfor %}
 
@@ -109,56 +129,60 @@
 <table class="table">
   <thead>
     <tr>
-    <th style="width:25%;%" scope="col" nowrap>Stimulus:</td>
-    <th style="width:25%;%" scope="col" nowrap>Picture:</th>
-    <th style="width:25%;%" scope="col" nowrap>Description:</th>
-    <th style="width:25%;%" scope="col" nowrap></th>
+      <th style="width:25%;%" scope="col" nowrap>Stimulus:</td>
+      <th style="width:25%;%" scope="col" nowrap>Picture:</th>
+      <th style="width:25%;%" scope="col" nowrap>Description:</th>
+      <th style="width:25%;%" scope="col" nowrap></th>
     </tr>
   </thead>
   <tbody>
-    {% for s in stimulus_headers %}    
-      {% for embody_picture in embody_questions %}
-        <tr>
-
-          {% if s.type == 'text' %}
-            <td>{{ s.text }}</td>
-          {% elif s.type == 'picture' %}
-            <td><img src="/{{ s.media }}" class="thumbnail" /></td>
-          {% elif s.type == 'video' %}
-            <td>
-              <div class="embed-responsive embed-responsive-16by9 ">
-                  <iframe class="embed-responsive-item thumbnail" src="/{{ s.media }}" allowFullScreen></iframe>
-              </div>
-            </td>
-          {% elif s.type == 'audio' %}
-            <td>
-              <div class="embed-responsive embed-responsive-16by9 ">
-                  <iframe class="embed-responsive-item thumbnail" src="/{{ s.media }}" allowFullScreen></iframe>
-              </div>
-            </td>
-          {% else %}
-            <td>{{ s.text }}</td>
-          {% endif %}
-
-          <td><img src="{{ embody_picture.picture }}" class="thumbnail" /></td>
-          <td>{{ embody_picture.question }}</td>
-
-          <td>
-          <button data-value="{{ s.idpage }}-{{ embody_picture.idembody }}" class="btn btn-primary embody-get-drawing">
-            <span class="spinner-border spinner-border-sm hidden"></span>
-            Draw
-          </button>
-          </td>
-
-        </tr>
-      {% endfor %}    
-    {% endfor %}    
+    {% for s in stimulus_headers %}
+    {% for embody_picture in embody_questions %}
+    <tr>
+
+      {% if s.type == 'text' %}
+      <td>{{ s.text }}</td>
+      {% elif s.type == 'picture' %}
+      <td><img src="/{{ s.media }}" class="thumbnail" /></td>
+      {% elif s.type == 'video' %}
+      <td>
+        <div class="embed-responsive embed-responsive-16by9 ">
+          <iframe class="embed-responsive-item thumbnail" src="/{{ s.media }}" allowFullScreen></iframe>
+        </div>
+      </td>
+      {% elif s.type == 'audio' %}
+      <td>
+        <div class="embed-responsive embed-responsive-16by9 ">
+          <audio class="embed-responsive-item thumbnail" controls>
+            <source src="/{{ s.media }}">
+            Your browser does not support the audio element.
+          </audio>
+        </div>
+      </td>
+      {% else %}
+      <td>{{ s.text }}</td>
+      {% endif %}
+
+      <td><img src="{{ embody_picture.picture }}" class="thumbnail" /></td>
+      <td>{{ embody_picture.question }}</td>
+
+      <td>
+        <button data-value="{{ s.idpage }}-{{ embody_picture.idembody }}" class="btn btn-primary embody-get-drawing">
+          <span class="spinner-border spinner-border-sm hidden"></span>
+          Draw
+        </button>
+      </td>
+
+    </tr>
+    {% endfor %}
+    {% endfor %}
   </tbody>
 </table>
 
 
 <div class="progress hidden" id="plotted-image">
-  <div id="image-loading-progress" class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" aria-valuenow="50" aria-valuemin="0" aria-valuemax="100" style="width: 0%">
+  <div id="image-loading-progress" class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar"
+    aria-valuenow="50" aria-valuemin="0" aria-valuemax="100" style="width: 0%">
     <!-- Creating image... -->
   </div>
 </div>
@@ -172,20 +196,20 @@
 <table class="table">
   <thead>
     <tr>
-     <th scope="col" nowrap>Participant</th>
-     
-     {% for bg in bg_questions %}
-     <th scope="col" nowrap>{{ bg.background_question }}</th>
-     {% endfor %}
+      <th scope="col" nowrap>Participant</th>
+
+      {% for bg in bg_questions %}
+      <th scope="col" nowrap>{{ bg.background_question }}</th>
+      {% endfor %}
     </tr>
   </thead>
   <tbody>
-    {% for p in bg_answers_for_participants %}    
+    {% for p in bg_answers_for_participants %}
     <tr>
-        <td>{{ p }}</td>
-        {% for bg_answer in bg_answers_for_participants[p] %}
-            <td align="center">{{ bg_answer }}</td>
-        {% endfor %}
+      <td>{{ p }}</td>
+      {% for bg_answer in bg_answers_for_participants[p] %}
+      <td align="center">{{ bg_answer }}</td>
+      {% endfor %}
     </tr>
     {% endfor %}
 
@@ -193,7 +217,9 @@
   </tbody>
 </table>
 
-<script src="{{ url_for('static', filename='lib/js/socket.io.js') }}" ></script>
-<script src="{{ url_for('static', filename='js/getDrawing.js') }}" ></script>
+<script src="{{ url_for('static', filename='lib/js/socket.io.js') }}"></script>
+<script src="{{ url_for('static', filename='js/urls.js') }}"></script>
+<script src="{{ url_for('static', filename='js/getDrawing.js') }}"></script>
+<script src="{{ url_for('static', filename='js/getCSV.js') }}"></script>
 
-{% endblock %}
+{% endblock %}
\ No newline at end of file
diff --git a/app/experiment/templates/view_experiment.html b/app/experiment/templates/view_experiment.html
index b3ee1a19262734852c5b61c91df1b477fd603e86..c0aee71c6757b95a426568161e9234cf3fac8779 100644
--- a/app/experiment/templates/view_experiment.html
+++ b/app/experiment/templates/view_experiment.html
@@ -211,7 +211,6 @@
     </table>
 
 
-
 <h1 class="container mt-5 display-4 text-left"><br>Add embody tool:</h1>
 
 <table class="table">
@@ -231,15 +230,13 @@
     {% endif %}
     </td>
     <td>
-      <a class="btn btn-primary btn-block btn-sm btn-info" href="{{ url_for('experiment.add_embody', exp_id=exp_id, default=true) }}" role="button">Add default</a>
       <a class="btn btn-primary btn-block btn-sm btn-info" href="{{ url_for('experiment.add_embody', exp_id=exp_id) }}" role="button">Add new picture</a>
     </td>
   </tr>
 
   {% for embody_picture in embody_pictures %}
-
   <tr>
-    <td>ID: {{ embody_picture.idembody }} <br> {{ embody_picture.question }}</td>
+    <td>ID: {{ embody_picture.idembody }} <br> {{ embody_picture.question }}</td> 
     <td><img src="{{ embody_picture.picture }}" class="thumbnail" /></td>
     <td><a class="btn btn-primary btn-block btn-sm btn-dark" href="{{ url_for('experiment.remove_embody', exp_id=exp_id, idembody=embody_picture.idembody) }}" role="button">Remove</a></td>
   </tr>
diff --git a/app/experiment/views.py b/app/experiment/views.py
index 6b02e510950e60e156b686cefddd00646103b2a7..110dfe95dbc6c92a00ebd032e6a7a81dd06f8e33 100644
--- a/app/experiment/views.py
+++ b/app/experiment/views.py
@@ -1,69 +1,71 @@
 
-
 import os
 import secrets
-import json
+from datetime import date
+from tempfile import mkstemp
 
+from flask_socketio import emit
+from sqlalchemy import and_
+from flask_login import login_required
+from werkzeug import secure_filename
 from flask import (
-    Flask, 
-    render_template, 
-    request, 
-    session, 
-    flash, 
-    redirect, 
-    url_for, 
+    render_template,
+    request,
+    flash,
+    redirect,
+    url_for,
     Blueprint,
-    jsonify
+    send_file
 )
 
-from wtforms import Form
-from sqlalchemy import and_, update
-from flask_login import login_required
-
-from app import app, db 
+from app import app, db, socketio
 from app.routes import APP_ROOT
 from app.models import background_question, experiment
 from app.models import background_question_answer
 from app.models import page, question
 from app.models import background_question_option
 from app.models import answer_set, answer, forced_id
-from app.models import user, trial_randomization
+from app.models import trial_randomization
 from app.models import embody_answer, embody_question
 from app.forms import (
-    CreateBackgroundQuestionForm, 
-    CreateQuestionForm, UploadStimuliForm, EditBackgroundQuestionForm, 
-    EditQuestionForm, EditExperimentForm, UploadResearchBulletinForm, 
-    EditPageForm, RemoveExperimentForm, GenerateIdForm,CreateEmbodyForm
+    CreateBackgroundQuestionForm,
+    CreateQuestionForm, UploadStimuliForm, EditBackgroundQuestionForm,
+    EditQuestionForm, EditExperimentForm, UploadResearchBulletinForm,
+    EditPageForm, RemoveExperimentForm, GenerateIdForm, CreateEmbodyForm
 )
-from werkzeug import secure_filename
+from app.utils import get_mean_from_slider_answers, map_answers_to_questions, \
+    generate_csv
+
+import embody_plot
 
-#Stimuli upload folder setting
+# Stimuli upload folder setting
 #APP_ROOT = os.path.dirname(os.path.abspath(__file__))
 
-experiment_blueprint = Blueprint("experiment", __name__, 
-                template_folder='templates',
-                #static_folder='static',
-                url_prefix='/experiment')
+experiment_blueprint = Blueprint("experiment", __name__,
+                                 template_folder='templates',
+                                 # static_folder='static',
+                                 url_prefix='/experiment')
 
 # Set sliders/embody:
 DEFAULT_EMBODY_PICTURE = '/static/img/dummy_600.png'
 DEFAULT_EMBODY_QUESTION = 'Color the regions whose activity you feel increasing or getting stronger'
 
+
 @experiment_blueprint.route('/view')
 @login_required
 def view():
-    
-    #crap:3lines
+
+    # crap:3lines
     exp_id = request.args.get('exp_id', None)
     media = page.query.filter_by(experiment_idexperiment=exp_id).all()
 
-    # stimulus type 
+    # stimulus type
     mtype = page.query.filter_by(experiment_idexperiment=exp_id).first()
-    
-    #experiment info    
-    experiment_info = experiment.query.filter_by(idexperiment = exp_id).all()
 
-    #background questions
+    # experiment info
+    experiment_info = experiment.query.filter_by(idexperiment=exp_id).all()
+
+    # background questions
     questions_and_options = {}
     questions = background_question.query.filter_by(
         experiment_idexperiment=exp_id).all()
@@ -79,7 +81,7 @@ def view():
 
     questions1 = questions_and_options
 
-    #sliderset
+    # sliderset
     categories_and_scales = {}
     categories = question.query.filter_by(experiment_idexperiment=exp_id).all()
 
@@ -107,51 +109,59 @@ def remove():
     exp_status = experiment.query.filter_by(idexperiment=exp_id).first()
 
     if exp_status.status != 'Hidden':
-    
+
         flash("Experiment is public. Cannot modify structure.")
         return redirect(url_for('experiment.view', exp_id=exp_id))
-        
+
     else:
-    
+
         form = RemoveExperimentForm(request.form)
-        
+
         if request.method == 'POST' and form.validate():
-    
+
             if form.remove.data == 'DELETE':
-    
-                #This removes all experiment data from the database!
-                
-                #Remove research bulletin if it exists
-                empty_filevariable = experiment.query.filter_by(idexperiment=exp_id).first()
-    
+
+                # This removes all experiment data from the database!
+
+                # Remove research bulletin if it exists
+                empty_filevariable = experiment.query.filter_by(
+                    idexperiment=exp_id).first()
+
                 if empty_filevariable.research_notification_filename is not None:
-                    target = os.path.join(APP_ROOT, empty_filevariable.research_notification_filename)
-                    
-                    if os.path.exists(target):                   
+                    target = os.path.join(
+                        APP_ROOT, empty_filevariable.research_notification_filename)
+
+                    if os.path.exists(target):
                         os.remove(target)
-                
-                #Tables
-                remove_forced_id = forced_id.query.filter_by(experiment_idexperiment=exp_id).all()
+
+                # Tables
+                remove_forced_id = forced_id.query.filter_by(
+                    experiment_idexperiment=exp_id).all()
                 remove_rows(remove_forced_id)
 
-                #background_question_option & background_question & background question answers:
-                remove_background_question = background_question.query.filter_by(experiment_idexperiment=exp_id).all()
-                
-                #Remove all background questions and all answers given to each bg question
+                # background_question_option & background_question & background question answers:
+                remove_background_question = background_question.query.filter_by(
+                    experiment_idexperiment=exp_id).all()
+
+                # Remove all background questions and all answers given to each bg question
                 for a in range(len(remove_background_question)):
-                    remove_background_question_option = background_question_option.query.filter_by(background_question_idbackground_question=remove_background_question[a].idbackground_question).all() 
+                    remove_background_question_option = background_question_option.query.filter_by(
+                        background_question_idbackground_question=remove_background_question[a].idbackground_question).all()
                     remove_rows(remove_background_question_option)
 
-                    remove_background_question_answers = background_question_answer.query.filter_by(background_question_idbackground_question=remove_background_question[a].idbackground_question).all()
+                    remove_background_question_answers = background_question_answer.query.filter_by(
+                        background_question_idbackground_question=remove_background_question[a].idbackground_question).all()
                     remove_rows(remove_background_question_answers)
 
                     db.session.delete(remove_background_question[a])
                     db.session.commit()
-               
-                #Remove all questions and answers 
-                remove_question = question.query.filter_by(experiment_idexperiment=exp_id).all()
+
+                # Remove all questions and answers
+                remove_question = question.query.filter_by(
+                    experiment_idexperiment=exp_id).all()
                 for a in range(len(remove_question)):
-                    remove_question_answers = answer.query.filter_by(question_idquestion=remove_question[a].idquestion).all()
+                    remove_question_answers = answer.query.filter_by(
+                        question_idquestion=remove_question[a].idquestion).all()
                     remove_rows(remove_question_answers)
                     db.session.delete(remove_question[a])
                     db.session.commit()
@@ -160,54 +170,58 @@ def remove():
                 remove_embody_answers = embody_answer.query.filter(embody_answer.page_idpage.in_(list(map(
                     lambda x: x[0], page.query.with_entities(page.idpage).filter_by(experiment_idexperiment=exp_id).all())))).all()
                 remove_rows(remove_embody_answers)
-                remove_embody_questions = embody_question.query.filter_by(experiment_idexperiment=exp_id).all()
+                remove_embody_questions = embody_question.query.filter_by(
+                    experiment_idexperiment=exp_id).all()
 
                 for a in range(len(remove_embody_questions)):
                     target = APP_ROOT + remove_embody_questions[a].picture
-                    if os.path.exists(target) and DEFAULT_EMBODY_PICTURE != remove_embody_questions[a].picture:                   
+                    if os.path.exists(target) and DEFAULT_EMBODY_PICTURE != remove_embody_questions[a].picture:
                         os.remove(target)
 
                 remove_rows(remove_embody_questions)
 
-                #Remove all pages and datafiles
-                remove_pages = page.query.filter_by(experiment_idexperiment=exp_id).all()
-                
+                # Remove all pages and datafiles
+                remove_pages = page.query.filter_by(
+                    experiment_idexperiment=exp_id).all()
+
                 for a in range(len(remove_pages)):
                     if remove_pages[a].type != 'text':
                         target = os.path.join(APP_ROOT, remove_pages[a].media)
-                        if os.path.exists(target):                   
+                        if os.path.exists(target):
                             os.remove(target)
-        
-                    #Now that the files are removed we can delete the page
+
+                    # Now that the files are removed we can delete the page
                     db.session.delete(remove_pages[a])
                     db.session.commit()
 
-                    
-                #Remove all answer_sets and trial_randomization orders
-                remove_answer_set = answer_set.query.filter_by(experiment_idexperiment=exp_id).all()
-                
+                # Remove all answer_sets and trial_randomization orders
+                remove_answer_set = answer_set.query.filter_by(
+                    experiment_idexperiment=exp_id).all()
+
                 for a in range(len(remove_answer_set)):
-                    remove_trial_randomizations = trial_randomization.query.filter_by(answer_set_idanswer_set=remove_answer_set[a].idanswer_set).all()
+                    remove_trial_randomizations = trial_randomization.query.filter_by(
+                        answer_set_idanswer_set=remove_answer_set[a].idanswer_set).all()
                     remove_rows(remove_trial_randomizations)
                     db.session.delete(remove_answer_set[a])
                     db.session.commit()
-                
-                #Remove experiment table
-                remove_experiment = experiment.query.filter_by(idexperiment=exp_id).first()
+
+                # Remove experiment table
+                remove_experiment = experiment.query.filter_by(
+                    idexperiment=exp_id).first()
                 db.session.delete(remove_experiment)
                 db.session.commit()
 
                 # Remove empty directories
                 os.rmdir(APP_ROOT + '/static/embody_images/' + str(exp_id))
                 os.rmdir(APP_ROOT + '/static/experiment_stimuli/' + str(exp_id))
-                
+
                 flash("Experiment was removed from database!")
                 return redirect(url_for('index'))
-                
+
             else:
                 flash("Experiment was not removed!")
                 return redirect(url_for('experiment.view', exp_id=exp_id))
-        
+
         return render_template('remove_experiment.html', form=form, exp_id=exp_id)
 
 
@@ -215,7 +229,8 @@ def remove():
 @login_required
 def publish():
     exp_id = request.args.get('exp_id', None)
-    publish_experiment = experiment.query.filter_by(idexperiment = exp_id).first()
+    publish_experiment = experiment.query.filter_by(
+        idexperiment=exp_id).first()
     publish_experiment.status = 'Public'
     flash("Changed status to Public")
     db.session.commit()
@@ -226,7 +241,7 @@ def publish():
 @login_required
 def hide():
     exp_id = request.args.get('exp_id', None)
-    hide_experiment = experiment.query.filter_by(idexperiment = exp_id).first()
+    hide_experiment = experiment.query.filter_by(idexperiment=exp_id).first()
     hide_experiment.status = 'Hidden'
     flash("Changed status to Hidden")
     db.session.commit()
@@ -237,7 +252,8 @@ def hide():
 @login_required
 def private():
     exp_id = request.args.get('exp_id', None)
-    private_experiment = experiment.query.filter_by(idexperiment = exp_id).first()
+    private_experiment = experiment.query.filter_by(
+        idexperiment=exp_id).first()
     private_experiment.status = 'Private'
     flash("Changed status to Private")
     db.session.commit()
@@ -255,9 +271,10 @@ def randomization():
     elif status == 'Off':
         flash("Disabled trial randomization")
 
-    experiment.query.filter_by(idexperiment = exp_id).first().randomization = status
+    experiment.query.filter_by(
+        idexperiment=exp_id).first().randomization = status
     db.session.commit()
-  
+
     return redirect(url_for('experiment.view', exp_id=exp_id))
 
 
@@ -275,9 +292,10 @@ def set_forced_id():
     elif status == 'Off':
         flash("Disabled forced ID login")
 
-    experiment.query.filter_by(idexperiment = exp_id).first().use_forced_id = status
+    experiment.query.filter_by(
+        idexperiment=exp_id).first().use_forced_id = status
     db.session.commit()
-  
+
     return redirect(url_for('experiment.view', exp_id=exp_id))
 
 
@@ -289,29 +307,34 @@ def view_forced_id_list():
     exp_id = request.args.get('exp_id', None)
     id_list = forced_id.query.filter_by(experiment_idexperiment=exp_id).all()
     form = GenerateIdForm(request.form)
-        
+
     if request.method == 'POST' and form.validate():
 
-        for i in range(int(request.form['number'])): 
+        for i in range(int(request.form['number'])):
             random_id = str(request.form['string']) + str(secrets.token_hex(3))
-            check_answer_set = answer_set.query.filter_by(session=random_id).first()
-            check_forced_id = forced_id.query.filter_by(pregenerated_id=random_id).first()
-            
-            #here we check if the generated id is found from given answers from the whole database in answer_set table
-            #or from forced_id table. If so another id is generated instead to avoid a duplicate
+            check_answer_set = answer_set.query.filter_by(
+                session=random_id).first()
+            check_forced_id = forced_id.query.filter_by(
+                pregenerated_id=random_id).first()
+
+            # here we check if the generated id is found from given answers from the whole database in answer_set table
+            # or from forced_id table. If so another id is generated instead to avoid a duplicate
             if check_answer_set is not None or check_forced_id is not None:
-            
+
                 #flash("ID already existed; generated a new one")
                 random_id = secrets.token_hex(3)
-                check_answer_set = answer_set.query.filter_by(session=random_id).first()
-                check_forced_id = forced_id.query.filter_by(pregenerated_id=random_id).first()   
-                
-            input_id = forced_id(experiment_idexperiment=exp_id, pregenerated_id=random_id)
+                check_answer_set = answer_set.query.filter_by(
+                    session=random_id).first()
+                check_forced_id = forced_id.query.filter_by(
+                    pregenerated_id=random_id).first()
+
+            input_id = forced_id(
+                experiment_idexperiment=exp_id, pregenerated_id=random_id)
             db.session.add(input_id)
             db.session.commit()
-        
+
         return redirect(url_for('experiment.view_forced_id_list', exp_id=exp_id))
-    
+
     return render_template('view_forced_id_list.html', exp_id=exp_id, id_list=id_list)
 
 
@@ -322,27 +345,27 @@ def upload_research_notification():
 
     exp_id = request.args.get('exp_id', None)
     form = UploadResearchBulletinForm(request.form)
-    
+
     if request.method == 'POST':
         path = 'static/experiment_stimuli/' + str(exp_id)
         target = os.path.join(APP_ROOT, path)
-                
+
         if not os.path.isdir(target):
             os.mkdir(target)
-            
-        #This returns a list of filenames: request.files.getlist("file")
+
+        # This returns a list of filenames: request.files.getlist("file")
         for file in request.files.getlist("file"):
-            #save files in the correct folder
+            # save files in the correct folder
             filename = file.filename
             destination = "/".join([target, filename])
             file.save(destination)
-            
-            #add pages to the db
-            db_path = path +  str('/') + str(filename)
+
+            # add pages to the db
+            db_path = path + str('/') + str(filename)
             bulletin = experiment.query.filter_by(idexperiment=exp_id).first()
             bulletin.research_notification_filename = db_path
             db.session.commit()
-            
+
         return redirect(url_for('experiment.view', exp_id=exp_id))
 
     return render_template('upload_research_notification.html', exp_id=exp_id, form=form)
@@ -352,17 +375,19 @@ def upload_research_notification():
 @login_required
 def remove_research_notification():
     '''Remove research bulletin'''
-    
+
     exp_id = request.args.get('exp_id', None)
-    empty_filevariable = experiment.query.filter_by(idexperiment=exp_id).first()
-    target = os.path.join(APP_ROOT, empty_filevariable.research_notification_filename)
-    
-    if os.path.exists(target):                   
+    empty_filevariable = experiment.query.filter_by(
+        idexperiment=exp_id).first()
+    target = os.path.join(
+        APP_ROOT, empty_filevariable.research_notification_filename)
+
+    if os.path.exists(target):
         os.remove(target)
-    
+
     empty_filevariable.research_notification_filename = None
     db.session.commit()
-    
+
     return redirect(url_for('experiment.view', exp_id=exp_id))
 
 
@@ -372,22 +397,20 @@ def edit():
     '''Edit experiment details'''
 
     exp_id = request.args.get('exp_id', None)
-    current_experiment = experiment.query.filter_by(idexperiment=exp_id).first()
-    
+    current_experiment = experiment.query.filter_by(
+        idexperiment=exp_id).first()
+
     form = EditExperimentForm(request.form, obj=current_experiment)
     form.language.default = current_experiment.language
-    
+
     if request.method == 'POST' and form.validate():
-        
+
         form.populate_obj(current_experiment)
         db.session.commit()
-        
-        return redirect(url_for('experiment.view', exp_id=exp_id))
-    
-    return render_template('edit_experiment.html', form=form, exp_id=exp_id)
-
 
+        return redirect(url_for('experiment.view', exp_id=exp_id))
 
+    return render_template('edit_experiment.html', form=form, exp_id=exp_id)
 
 
 # Background questions:
@@ -395,94 +418,100 @@ def edit():
 @experiment_blueprint.route('/add_bg_question', methods=['GET', 'POST'])
 @login_required
 def add_bg_question():
-    
+
     exp_id = request.args.get('exp_id', None)
     exp_status = experiment.query.filter_by(idexperiment=exp_id).first()
 
     if exp_status.status != 'Hidden':
         flash("Experiment is public. Cannot modify structure.")
         return redirect(url_for('experiment.view', exp_id=exp_id))
-        
+
     else:
         form = CreateBackgroundQuestionForm(request.form)
-        
+
         if request.method == 'POST' and form.validate():
-            
-            #Split the form data into a list that separates questions followed by the corresponding options
+
+            # Split the form data into a list that separates questions followed by the corresponding options
             str = form.bg_questions_and_options.data
             str_list = str.split('/n')
-    
-            #Iterate through the questions and options list
+
+            # Iterate through the questions and options list
             for a in range(len(str_list)):
-                #Split the list cells further into questions and options
+                # Split the list cells further into questions and options
                 list = str_list[a].split(';')
-            
-                #Input the first item of the list as a question in db and the items followed by that as options for that question
+
+                # Input the first item of the list as a question in db and the items followed by that as options for that question
                 for x in range(len(list)):
                     if x == 0:
-                        add_bgquestion = background_question(background_question=list[x], experiment_idexperiment=exp_id)
+                        add_bgquestion = background_question(
+                            background_question=list[x], experiment_idexperiment=exp_id)
                         db.session.add(add_bgquestion)
                         db.session.commit()
                     else:
-                        add_bgq_option = background_question_option(background_question_idbackground_question=add_bgquestion.idbackground_question, option=list[x])
+                        add_bgq_option = background_question_option(
+                            background_question_idbackground_question=add_bgquestion.idbackground_question, option=list[x])
                         db.session.add(add_bgq_option)
                         db.session.commit()
-            
-            return redirect(url_for('experiment.view', exp_id=exp_id))    
-    
+
+            return redirect(url_for('experiment.view', exp_id=exp_id))
+
         return render_template('add_bg_question.html', form=form)
 
 
 @experiment_blueprint.route('/edit_bg_question', methods=['GET', 'POST'])
 @login_required
 def edit_bg_question():
-    
+
     bg_question_id = request.args.get('idbackground_question', None)
 
-    #Search for the right question and for the right options. Form a string of those separated with ";" and insert the
-    #formed string into the edit form        
-    current_bg_question = background_question.query.filter_by(idbackground_question=bg_question_id).first()
-    exp_id=current_bg_question.experiment_idexperiment
+    # Search for the right question and for the right options. Form a string of those separated with ";" and insert the
+    # formed string into the edit form
+    current_bg_question = background_question.query.filter_by(
+        idbackground_question=bg_question_id).first()
+    exp_id = current_bg_question.experiment_idexperiment
     question_string = current_bg_question.background_question
-    options  = background_question_option.query.filter_by(background_question_idbackground_question=bg_question_id).all()    
-    
-    for o in range(len(options)):    
-        question_string = str(question_string) + str("; ") + str(options[o].option)
-    
+    options = background_question_option.query.filter_by(
+        background_question_idbackground_question=bg_question_id).all()
+
+    for o in range(len(options)):
+        question_string = str(question_string) + \
+            str("; ") + str(options[o].option)
+
     form = EditBackgroundQuestionForm(request.form)
     form.bg_questions_and_options.data = question_string
 
-    #After user chooses to update the question and options lets replace the old question and options with the ones from the form
+    # After user chooses to update the question and options lets replace the old question and options with the ones from the form
     if request.method == 'POST' and form.validate():
-        #Explode the string with new values from the form 
+        # Explode the string with new values from the form
         form_values = form.new_values.data
         form_values_list = form_values.split(';')
-        
-        #Check and remove possible whitespaces from string beginnings with lstrip
+
+        # Check and remove possible whitespaces from string beginnings with lstrip
         for x in range(len(form_values_list)):
             form_values_list[x] = form_values_list[x].lstrip()
-        
-        #Cycle through strings and update db
+
+        # Cycle through strings and update db
         for x in range(len(form_values_list)):
-        
-            #Replace question and update the object to database
+
+            # Replace question and update the object to database
             if x == 0:
-                current_bg_question.background_question  = form_values_list[x] 
+                current_bg_question.background_question = form_values_list[x]
                 db.session.commit()
-                
-                #Delete old options from db
+
+                # Delete old options from db
                 for o in options:
                     db.session.delete(o)
                     db.session.commit()
-            
-            #Insert new options to db
+
+            # Insert new options to db
             else:
-                new_option = background_question_option(background_question_idbackground_question=current_bg_question.idbackground_question, option=form_values_list[x])
+                new_option = background_question_option(
+                    background_question_idbackground_question=current_bg_question.idbackground_question, option=form_values_list[x])
                 db.session.add(new_option)
                 db.session.commit()
-        
+
         return redirect(url_for('experiment.view', exp_id=exp_id))
-    
+
     return render_template('edit_bg_question.html', form=form, exp_id=exp_id)
 
 
@@ -496,24 +525,24 @@ def remove_bg_question():
     if exp_status.status != 'Hidden':
         flash("Experiment is public. Cannot modify structure.")
         return redirect(url_for('experiment.view', exp_id=exp_id))
-        
+
     else:
 
         remove_id = request.args.get('idbackground_question', None)
-        remove_options = background_question_option.query.filter_by(background_question_idbackground_question=remove_id).all()
-        
-        for a in range(len(remove_options)): 
+        remove_options = background_question_option.query.filter_by(
+            background_question_idbackground_question=remove_id).all()
+
+        for a in range(len(remove_options)):
             db.session.delete(remove_options[a])
             db.session.commit()
-            
-        remove_question = background_question.query.filter_by(idbackground_question=remove_id).first()
-        
+
+        remove_question = background_question.query.filter_by(
+            idbackground_question=remove_id).first()
+
         db.session.delete(remove_question)
         db.session.commit()
-  
-    return redirect(url_for('experiment.view', exp_id=exp_id))
-
 
+    return redirect(url_for('experiment.view', exp_id=exp_id))
 
 
 @experiment_blueprint.route('/set_embody')
@@ -521,7 +550,7 @@ def remove_bg_question():
 def set_embody():
     '''Enable/disable embody tool'''
     exp_id = request.args.get('exp_id', None)
-    exp = experiment.query.filter_by(idexperiment = exp_id).first()
+    exp = experiment.query.filter_by(idexperiment=exp_id).first()
     exp.embody_enabled = (True if exp.embody_enabled == False else False)
     return redirect(url_for('experiment.view', exp_id=exp_id))
 
@@ -537,16 +566,6 @@ def add_embody():
     if exp_info.status != 'Hidden':
         flash("Experiment is public. Cannot modify structure.")
         return redirect(url_for('experiment.view', exp_id=exp_id))
-    elif default:
-
-        # TODO: check if default image already added
-
-        default_embody = embody_question(experiment_idexperiment=exp_id, picture=DEFAULT_EMBODY_PICTURE, question=DEFAULT_EMBODY_QUESTION)
-        db.session.add(default_embody) 
-        exp_info.embody_enabled = 1
-        db.session.commit() 
-        return redirect(url_for('experiment.view', exp_id=exp_id))
-
     else:
         form = CreateEmbodyForm(request.form)
 
@@ -554,10 +573,18 @@ def add_embody():
             picture = request.files.get("picture")
             question = request.form.get("question")
 
-            # get filename 
+            if not picture:
+                default_embody = embody_question(
+                    experiment_idexperiment=exp_id, picture=DEFAULT_EMBODY_PICTURE, question=question)
+                db.session.add(default_embody)
+                exp_info.embody_enabled = 1
+                db.session.commit()
+                return redirect(url_for('experiment.view', exp_id=exp_id))
+
+            # get filename
             filename = secure_filename(picture.filename)
             path = 'static/embody_images/' + str(exp_id)
-            db_path = '/' + path +  str('/') + str(filename)
+            db_path = '/' + path + str('/') + str(filename)
             target = os.path.join(APP_ROOT, path)
 
             # create folder with experiment id (if it does not exist)
@@ -568,14 +595,13 @@ def add_embody():
             destination = "/".join([target, filename])
             picture.save(destination)
 
-            #add pages to the db
-            new_embody = embody_question(experiment_idexperiment=exp_id, question=question, picture=db_path)
+            # add pages to the db
+            new_embody = embody_question(
+                experiment_idexperiment=exp_id, question=question, picture=db_path)
             db.session.add(new_embody)
             exp_info.embody_enabled = 1
             db.session.commit()
-
-
-            return redirect(url_for('experiment.view', exp_id=exp_id))    
+            return redirect(url_for('experiment.view', exp_id=exp_id))
 
         return render_template('add_embody.html', form=form)
 
@@ -583,7 +609,7 @@ def add_embody():
 @experiment_blueprint.route('/add_questions', methods=['GET', 'POST'])
 @login_required
 def add_questions():
-    
+
     exp_id = request.args.get('exp_id', None)
     exp_status = experiment.query.filter_by(idexperiment=exp_id).first()
 
@@ -592,47 +618,49 @@ def add_questions():
         return redirect(url_for('experiment.view', exp_id=exp_id))
     else:
         form = CreateQuestionForm(request.form)
-        
+
         if request.method == 'POST' and form.validate():
-    
+
             str = form.questions_and_options.data
             str_list = str.split('/n')
-    
-            for a in range(len(str_list)): 
+
+            for a in range(len(str_list)):
                 list = str_list[a].split(';')
-                         
-                #If there are the right amount of values for the slider input values
+
+                # If there are the right amount of values for the slider input values
                 if len(list) == 3:
-                    add_question = question(experiment_idexperiment=exp_id, question=list[0], left=list[1], right=list[2])
+                    add_question = question(
+                        experiment_idexperiment=exp_id, question=list[0], left=list[1], right=list[2])
                     db.session.add(add_question)
                     db.session.commit()
-                        
-                #If slider has too many or too litlle parameters give an error and redirect back to input form
+
+                # If slider has too many or too litlle parameters give an error and redirect back to input form
                 else:
-                    flash("Error Each slider must have 3 parameters separated by ; Some slider has:")
+                    flash(
+                        "Error Each slider must have 3 parameters separated by ; Some slider has:")
                     flash(len(list))
                     return redirect(url_for('create.experiment_questions', exp_id=exp_id))
-            
-            return redirect(url_for('experiment.view', exp_id=exp_id))    
-    
+
+            return redirect(url_for('experiment.view', exp_id=exp_id))
+
         return render_template('add_questions.html', form=form)
 
 
 @experiment_blueprint.route('/edit_question', methods=['GET', 'POST'])
 @login_required
 def edit_question():
-    
+
     question_id = request.args.get('idquestion', None)
     current_question = question.query.filter_by(idquestion=question_id).first()
     form = EditQuestionForm(request.form, obj=current_question)
-    
+
     if request.method == 'POST' and form.validate():
-        
+
         form.populate_obj(current_question)
         db.session.commit()
-        
+
         return redirect(url_for('experiment.view', exp_id=current_question.experiment_idexperiment))
-  
+
     return render_template('edit_question.html', form=form)
 
 
@@ -646,21 +674,23 @@ def remove_question():
     if exp_status.status != 'Hidden':
         flash("Experiment is public. Cannot modify structure.")
         return redirect(url_for('experiment.view', exp_id=exp_id))
-        
+
     else:
 
         remove_id = request.args.get('idquestion', None)
-        remove_question = question.query.filter_by(idquestion=remove_id).first()
+        remove_question = question.query.filter_by(
+            idquestion=remove_id).first()
 
         # remove answers before removing questions
-        remove_answers = answer.query.filter_by(question_idquestion=remove_question.idquestion).all()
+        remove_answers = answer.query.filter_by(
+            question_idquestion=remove_question.idquestion).all()
         for a in range(len(remove_answers)):
             db.session.delete(remove_answers[a])
             db.session.commit()
-                    
+
         db.session.delete(remove_question)
         db.session.commit()
-  
+
     return redirect(url_for('experiment.view', exp_id=exp_id))
 
 
@@ -674,29 +704,30 @@ def remove_embody():
     if exp_status.status != 'Hidden':
         flash("Experiment is public. Cannot modify structure.")
         return redirect(url_for('experiment.view', exp_id=exp_id))
-        
+
     else:
         remove_id = request.args.get('idembody', None)
-        remove_question = embody_question.query.filter_by(idembody=remove_id).first()
+        remove_question = embody_question.query.filter_by(
+            idembody=remove_id).first()
 
         # remove embody image from server
         if DEFAULT_EMBODY_PICTURE != remove_question.picture:
             os.remove(APP_ROOT + remove_question.picture)
 
-        remove_answers = embody_answer.query.filter_by(embody_question_idembody=remove_question.idembody).all()
+        remove_answers = embody_answer.query.filter_by(
+            embody_question_idembody=remove_question.idembody).all()
         remove_rows(remove_answers)
         db.session.delete(remove_question)
         db.session.commit()
 
-    
-    question_count = embody_question.query.filter_by(experiment_idexperiment=exp_id).count()
+    question_count = embody_question.query.filter_by(
+        experiment_idexperiment=exp_id).count()
 
     if question_count == 0:
         exp_status.embody_enabled = 0
         db.session.commit()
-  
-    return redirect(url_for('experiment.view', exp_id=exp_id))
 
+    return redirect(url_for('experiment.view', exp_id=exp_id))
 
 
 # Stimuli:
@@ -707,56 +738,59 @@ def add_stimuli():
 
     exp_id = request.args.get('exp_id', None)
     exp_status = experiment.query.filter_by(idexperiment=exp_id).first()
-    
+
     if exp_status.status != 'Hidden':
         flash("Experiment is public. Cannot modify structure.")
         return redirect(url_for('experiment.view', exp_id=exp_id))
     else:
-        #If there are no pages set for the experiment lets reroute user to create experiment stimuli upload instead
-        is_there_any_stimuli = page.query.filter_by(experiment_idexperiment = exp_id).first()
-    
+        # If there are no pages set for the experiment lets reroute user to create experiment stimuli upload instead
+        is_there_any_stimuli = page.query.filter_by(
+            experiment_idexperiment=exp_id).first()
+
         if is_there_any_stimuli is None:
             return redirect(url_for('create.experiment_upload_stimuli', exp_id=exp_id))
-        
+
         stimulus_type = request.args.get('stimulus_type', None)
         form = UploadStimuliForm(request.form)
-    
+
         if request.method == 'POST':
             if stimulus_type == 'text':
                 string = form.text.data
                 str_list = string.split('/n')
-    
+
                 for a in range(len(str_list)):
-                    add_text_stimulus = page(experiment_idexperiment=exp_id, type='text', text=str_list[a], media='none')
+                    add_text_stimulus = page(
+                        experiment_idexperiment=exp_id, type='text', text=str_list[a], media='none')
                     db.session.add(add_text_stimulus)
                     db.session.commit()
-                    
-                return redirect(url_for('experiment.view', exp_id=exp_id))  
-            
+
+                return redirect(url_for('experiment.view', exp_id=exp_id))
+
             else:
-                #Upload stimuli into /static/experiment_stimuli/exp_id folder
-                #Create the pages for the stimuli by inserting experiment_id, stimulus type, text and names of the stimulus files (as a path to the folder)
+                # Upload stimuli into /static/experiment_stimuli/exp_id folder
+                # Create the pages for the stimuli by inserting experiment_id, stimulus type, text and names of the stimulus files (as a path to the folder)
                 path = 'static/experiment_stimuli/' + str(exp_id)
                 target = os.path.join(APP_ROOT, path)
-                
+
                 if not os.path.isdir(target):
                     os.mkdir(target)
-            
-                #This returns a list of filenames: request.files.getlist("file")
+
+                # This returns a list of filenames: request.files.getlist("file")
                 for file in request.files.getlist("file"):
-                    #save files in the correct folder
+                    # save files in the correct folder
                     filename = file.filename
                     destination = "/".join([target, filename])
                     file.save(destination)
 
-                    #add pages to the db
-                    db_path = path +  str('/') + str(filename)
-                    new_page = page(experiment_idexperiment=exp_id, type=form.type.data, media=db_path)
+                    # add pages to the db
+                    db_path = path + str('/') + str(filename)
+                    new_page = page(experiment_idexperiment=exp_id,
+                                    type=form.type.data, media=db_path)
                     db.session.add(new_page)
                     db.session.commit()
-                    
+
                 return redirect(url_for('experiment.view', exp_id=exp_id))
-                
+
             return redirect(url_for('experiment.view', exp_id=exp_id))
 
         return render_template('add_stimuli.html', form=form, stimulus_type=stimulus_type)
@@ -776,38 +810,38 @@ def edit_stimuli():
 
     if request.method == 'POST' and form.validate():
         print("POST IMAGE")
-        #If the stimulus type is not text, then the old stimulus file is deleted from os and replaced
+        # If the stimulus type is not text, then the old stimulus file is deleted from os and replaced
         if edit_page.type != 'text':
-            #remove old file            
+            # remove old file
             target = os.path.join(APP_ROOT, edit_page.media)
             os.remove(target)
 
-            #upload new file
+            # upload new file
             path = 'static/experiment_stimuli/' + str(exp_id)
             target = os.path.join(APP_ROOT, path)
-            
+
             if not os.path.isdir(target):
                 os.mkdir(target)
-        
-            #This returns a list of filenames: request.files.getlist("file")
+
+            # This returns a list of filenames: request.files.getlist("file")
             for file in request.files.getlist("file"):
-                #save files in the correct folder
+                # save files in the correct folder
                 filename = file.filename
                 destination = "/".join([target, filename])
                 file.save(destination)
-                
-                #update db object
-                db_path = path +  str('/') + str(filename)
-                edit_page.media=db_path
+
+                # update db object
+                db_path = path + str('/') + str(filename)
+                edit_page.media = db_path
                 db.session.commit()
 
-        #If editing text stimulus no need for filehandling    
+        # If editing text stimulus no need for filehandling
         else:
-             form.populate_obj(edit_page)
-             db.session.commit()
-                           
+            form.populate_obj(edit_page)
+            db.session.commit()
+
         return redirect(url_for('experiment.view', exp_id=exp_id))
-  
+
     return render_template('edit_stimuli.html', form=form, edit_page=edit_page)
 
 
@@ -819,65 +853,68 @@ def remove_stimuli():
     exp_status = experiment.query.filter_by(idexperiment=exp_id).first()
 
     if exp_status.status != 'Hidden':
-    
+
         flash("Experiment is public. Cannot modify structure.")
 
         return redirect(url_for('experiment.view', exp_id=exp_id))
-        
+
     else:
-    
+
         remove_id = request.args.get('idpage', None)
         remove_page = page.query.filter_by(idpage=remove_id).first()
-        experiment_pages = page.query.filter_by(experiment_idexperiment=exp_id).all()
-        
-        #if stimulustype is text, the stimulus itself is text on the database, other stimulus types are real files
-        #on the server and need to be deleted    
+        experiment_pages = page.query.filter_by(
+            experiment_idexperiment=exp_id).all()
+
+        # if stimulustype is text, the stimulus itself is text on the database, other stimulus types are real files
+        # on the server and need to be deleted
         if remove_page.type != 'text':
-            
-            #helper variable
+
+            # helper variable
             do_not_delete_file = 'False'
-                
-            #if the file to be deleted is in duplicate use of another page then we won't delete the file
+
+            # if the file to be deleted is in duplicate use of another page then we won't delete the file
             for a in range(len(experiment_pages)):
                 if experiment_pages[a].media == remove_page.media and experiment_pages[a].idpage != remove_page.idpage:
                     do_not_delete_file = 'True'
-    
-            #If no other page is using the file then lets remove it
+
+            # If no other page is using the file then lets remove it
             if do_not_delete_file == 'False':
                 target = os.path.join(APP_ROOT, remove_page.media)
                 os.remove(target)
-        
+
             # remove slider answer from this page
-            remove_question_answers = answer.query.filter_by(page_idpage=remove_page.idpage).all()
+            remove_question_answers = answer.query.filter_by(
+                page_idpage=remove_page.idpage).all()
             remove_rows(remove_question_answers)
 
             # remove embody answer from this page
-            remove_embody_answers = embody_answer.query.filter_by(page_idpage=remove_page.idpage).all()
+            remove_embody_answers = embody_answer.query.filter_by(
+                page_idpage=remove_page.idpage).all()
             remove_rows(remove_embody_answers)
 
             db.session.delete(remove_page)
             db.session.commit()
-      
+
             return redirect(url_for('experiment.view', exp_id=exp_id))
-        
-        
+
         if remove_page.type == 'text':
-            
+
             # remove slider answer from this page
-            remove_question_answers = answer.query.filter_by(page_idpage=remove_page.idpage).all()
+            remove_question_answers = answer.query.filter_by(
+                page_idpage=remove_page.idpage).all()
             remove_rows(remove_question_answers)
 
             # remove embody answer from this page
-            remove_embody_answers = embody_answer.query.filter_by(page_idpage=remove_page.idpage).all()
+            remove_embody_answers = embody_answer.query.filter_by(
+                page_idpage=remove_page.idpage).all()
             remove_rows(remove_embody_answers)
 
             db.session.delete(remove_page)
             db.session.commit()
-            
+
             return redirect(url_for('experiment.view', exp_id=exp_id))
-        
-        return redirect(url_for('experiment.view', exp_id=exp_id))
 
+        return redirect(url_for('experiment.view', exp_id=exp_id))
 
 
 # Misc:
@@ -887,13 +924,14 @@ def remove_stimuli():
 def statistics():
 
     # TODO: Answers are in normal order although questions might be in randomized order
-    
+
     exp_id = request.args.get('exp_id', None)
-    
-    experiment_info = experiment.query.filter_by(idexperiment = exp_id).all()
-    participants = answer_set.query.filter_by(experiment_idexperiment= exp_id).all()
-    
-    #started and finished ratings counters
+
+    experiment_info = experiment.query.filter_by(idexperiment=exp_id).all()
+    participants = answer_set.query.filter_by(
+        experiment_idexperiment=exp_id).all()
+
+    # started and finished ratings counters
     started_ratings = answer_set.query.filter_by(
         experiment_idexperiment=exp_id).count()
     experiment_page_count = page.query.filter_by(
@@ -901,66 +939,101 @@ def statistics():
     finished_ratings = answer_set.query.filter(and_(
         answer_set.answer_counter == experiment_page_count, answer_set.experiment_idexperiment == exp_id)).count()
 
-    
-    #Rating task headers
-    question_headers = question.query.filter_by(experiment_idexperiment=exp_id).all()
-    stimulus_headers = page.query.filter_by(experiment_idexperiment=exp_id).all()
-    
+    # Rating task headers
+    question_headers = question.query.filter_by(
+        experiment_idexperiment=exp_id).all()
+    stimulus_headers = page.query.filter_by(
+        experiment_idexperiment=exp_id).all()
+
     pages = page.query.filter_by(experiment_idexperiment=exp_id).all()
     questions = question.query.filter_by(experiment_idexperiment=exp_id).all()
     pages_and_questions = {}
 
+    '''
+
     for p in pages:
         questions_list = [(p.idpage, a.idquestion) for a in questions]
-        pages_and_questions[p.idpage] = questions_list 
-        
-    #List of answers per participant in format question Stimulus ID/Question ID
-    #those are in answer table as page_idpage and question_idquestion respectively
+        pages_and_questions[p.idpage] = questions_list
 
+    # List of answers per participant in format question Stimulus ID/Question ID
+    # those are in answer table as page_idpage and question_idquestion respectively
     slider_answers = {}
     for participant in participants:
 
-        # list only finished answer sets
-        if experiment_page_count == participant.answer_counter:
-            answers = answer.query.filter_by(answer_set_idanswer_set=participant.idanswer_set).all()     
-            slider_answers[participant.session] = [ a.answer for a in answers]     
+        if int(participant.answer_counter) == 0:
+            continue
+
+        answers = answer.query.filter_by(
+            answer_set_idanswer_set=participant.idanswer_set)\
+            .order_by(answer.page_idpage)\
+            .all()
+
+        # flatten pages and questions to list of tuples (page_id, question_id)
+        _questions = [
+            item for sublist in pages_and_questions.values() for item in sublist]
+
 
-    # map slider_answers from str to int and calculate mean
-    a = [map(int,i) for i in list(slider_answers.values())]
-    slider_answers['mean'] = [round(float(sum(l))/len(l), 2) for l in zip(*a)]
+        slider_answers[participant.session] = map_answers_to_questions(
+            answers, _questions)
 
+    mean = get_mean_from_slider_answers(slider_answers)
+    # slider_answers['mean'] = get_mean_from_slider_answers(slider_answers)
 
-    #Background question answers
+    slider_answers = {
+        'mean': mean
+    }
+
+    '''
+
+    slider_answers = {}
+
+    # Background question answers
     bg_questions = background_question.query.filter_by(
         experiment_idexperiment=exp_id).all()
     bg_answers_for_participants = {}
 
     for participant in participants:
-
-        # list only finished answer sets
-        if experiment_page_count == participant.answer_counter:
+        if participant.answer_counter > 0:
             bg_answers = background_question_answer.query.filter_by(
                 answer_set_idanswer_set=participant.idanswer_set).all()
             bg_answers_list = [(a.answer) for a in bg_answers]
             bg_answers_for_participants[participant.session] = bg_answers_list
 
-
     # embody questions
     embody_questions = embody_question.query.filter_by(
         experiment_idexperiment=exp_id).all()
 
-    return render_template('experiment_statistics.html', 
-        experiment_info=experiment_info, 
-        participants_and_answers=slider_answers,  
-        pages_and_questions=pages_and_questions, 
-        bg_questions=bg_questions, 
-        bg_answers_for_participants=bg_answers_for_participants,  
-        started_ratings=started_ratings, 
-        finished_ratings=finished_ratings, 
-        question_headers=question_headers, 
-        stimulus_headers=stimulus_headers, 
-        embody_questions=embody_questions
-    )
+    return render_template('experiment_statistics.html',
+                           experiment_info=experiment_info,
+                           participants_and_answers=slider_answers,
+                           pages_and_questions=pages_and_questions,
+                           bg_questions=bg_questions,
+                           bg_answers_for_participants=bg_answers_for_participants,
+                           started_ratings=started_ratings,
+                           finished_ratings=finished_ratings,
+                           question_headers=question_headers,
+                           stimulus_headers=stimulus_headers,
+                           embody_questions=embody_questions)
+
+
+@experiment_blueprint.route('/download_csv')
+def download_csv():
+    exp_id = request.args.get('exp_id', None)
+    path = request.args.get('path', None)
+
+    filename = "experiment_{}_{}.csv".format(
+        exp_id, date.today().strftime("%Y-%m-%d"))
+
+    path = '/tmp/' + path
+
+    try:
+        return send_file(path,
+                         mimetype='text/csv',
+                         as_attachment=True,
+                         attachment_filename=filename)
+
+    finally:
+        os.remove(path)
 
 
 def remove_rows(rows):
@@ -970,29 +1043,56 @@ def remove_rows(rows):
         db.session.commit()
 
 
-import embody_plot
-from flask_cors import CORS,cross_origin
-from flask_socketio import emit
-from app import socketio
-
-
 @socketio.on('connect', namespace="/create_embody")
-def create_embody():
+def start_create_embody():
     emit('success', {'connection': 'on'})
 
-@socketio.on('draw', namespace="/create_embody")
-def create_embody(page_id):
-    page = page_id["page"]
-    embody = page_id["embody"]
 
+@socketio.on('draw', namespace="/create_embody")
+def create_embody(meta):
+    page = meta["page"]
+    embody = meta["embody"]
     img_path = embody_plot.get_coordinates(page, embody)
     app.logger.info(img_path)
-    emit('end', {'path':img_path})
+    emit('end', {'path': img_path})
 
-@socketio.on('end', namespace="/create_embody")
-def create_embody():
-    print("connection end")
-    emit('end', {'connection': 'off'})
 
+@socketio.on('connect', namespace="/download_csv")
+def start_download_csv():
+    emit('success', {'connection': 'Start generating CSV file'})
+
+
+@socketio.on('generate_csv', namespace="/download_csv")
+def download_csv(meta):
+    exp_id = meta["exp_id"]
+
+    data = generate_csv(exp_id)
+
+    # error handling
+    if isinstance(data, Exception):
+        emit('timeout', {'exc': str(data)})
+        return
+
+    # create temporary file 
+    fd, path = mkstemp()
+    with os.fdopen(fd, 'w') as tmp:
+        tmp.write(data)
+        tmp.flush()
 
-# EOF
+    # return path and filename to front so user can start downloading
+    filename = "experiment_{}_{}".format(
+        exp_id, date.today().strftime("%Y-%m-%d"))
+    path = path.split('/')[-1]    
+    emit('file_ready', {'path': path, 'filename': filename})
+
+
+@socketio.on('end', namespace="/download_csv")
+def end_download_csv():
+    # TODO: not working solution... db session keeps hanging after socket session has ended
+    # mysqld timeout is set to 180s, so it kills hanging connections, but this is not a good solution 
+    db.session.close()
+
+
+@socketio.on('end', namespace="/create_embody")
+def end_create_embody():
+    db.session.close()
diff --git a/app/models.py b/app/models.py
index 6920ffaa20a1c611e9d837b5c8985d944b5410db..c792add29192ab35999a8898e477eff576fd505b 100644
--- a/app/models.py
+++ b/app/models.py
@@ -1,7 +1,9 @@
+from sqlalchemy import and_
+from flask import session
 from app import db
 from sqlalchemy import Column, Integer, String, Text, Boolean
 from flask_wtf import FlaskForm
-from wtforms_sqlalchemy.fields import QuerySelectField, QuerySelectMultipleField 
+from wtforms_sqlalchemy.fields import QuerySelectField, QuerySelectMultipleField
 from werkzeug.security import generate_password_hash, check_password_hash
 from flask_login import UserMixin
 from app import login
@@ -15,9 +17,10 @@ class background_question(db.Model):
     __tablename__ = "background_question"
     idbackground_question = db.Column(db.Integer, primary_key=True)
     background_question = db.Column(db.String(120))
-    answers = db.relationship('background_question_answer', backref='question', lazy='dynamic')
+    answers = db.relationship(
+        'background_question_answer', backref='question', lazy='dynamic')
     experiment_idexperiment = db.Column(db.Integer)
-    
+
     def __repr__(self):
         return "<idbackground_question = '%s', background_question = '%s'>" % (self.idbackground_question, self.background_question)
 
@@ -25,11 +28,12 @@ class background_question(db.Model):
 class background_question_option(db.Model):
     __tablename__ = "background_question_option"
     idbackground_question_option = db.Column(db.Integer, primary_key=True)
-    background_question_idbackground_question = db.Column(db.Integer, db.ForeignKey('background_question.idbackground_question'))
+    background_question_idbackground_question = db.Column(
+        db.Integer, db.ForeignKey('background_question.idbackground_question'))
     option = db.Column(db.String(120))
-    
+
     def __repr__(self):
-        return "<idbackground_question_option = '%s', background_question_idbackground_question = '%s',  option = '%s'>" % (self.idbackground_question_option, self.background_question_idbackground_question, self.option) 
+        return "<idbackground_question_option = '%s', background_question_idbackground_question = '%s',  option = '%s'>" % (self.idbackground_question_option, self.background_question_idbackground_question, self.option)
 
 
 class experiment (db.Model):
@@ -51,7 +55,7 @@ class experiment (db.Model):
     consent_text = db.Column(db.Text, index=True)
     use_forced_id = db.Column(db.String(120))
     embody_enabled = db.Column(db.Boolean, unique=False, default=False)
-    
+
     def __repr__(self):
         return "<idexperiment = '%s', name='%s', instruction='%s', directoryname='%s', language='%s', status='%s', randomization='%s', short_instruction='%s', single_sentence_instruction='%s', is_archived='%s', creator_name='%s', research_notification_filename='%s', creation_time='%s', stimulus_size='%s', consent_text='%s', use_forced_id='%s', embody_enabled='%s'>" % (self.idexperiment, self.name, self.instruction, self.directoryname, self.language, self.status, self.randomization, self.short_instruction, self.single_sentence_instruction, self.is_archived, self.creator_name, self.research_notification_filename, self.creation_time, self.stimulus_size, self.consent_text, self.use_forced_id, self.embody_enabled)
 
@@ -59,13 +63,16 @@ class experiment (db.Model):
 class answer_set (db.Model):
     __tablename__ = "answer_set"
     idanswer_set = db.Column(db.Integer, primary_key=True)
-    experiment_idexperiment = db.Column(db.Integer, db.ForeignKey('experiment.idexperiment'))
+    experiment_idexperiment = db.Column(
+        db.Integer, db.ForeignKey('experiment.idexperiment'))
     session = db.Column(db.String(120))
     agreement = db.Column(db.String(120))
     answer_counter = db.Column(db.Integer)
     answer_type = db.Column(db.String(120))
-    registration_time = db.Column(db.DateTime, index=True, default=datetime.utcnow)
-    last_answer_time = db.Column(db.DateTime, index=True, default=datetime.utcnow)
+    registration_time = db.Column(
+        db.DateTime, index=True, default=datetime.utcnow)
+    last_answer_time = db.Column(
+        db.DateTime, index=True, default=datetime.utcnow)
 
     def __repr__(self):
         return "<idanswer_set = '%s', experiment_idexperiment = '%s', session = '%s', agreement = '%s', answer_counter = '%s', registration_time = '%s', last_answer_time = '%s'>" % (self.idanswer_set, self.experiment_idexperiment, self.session, self.agreement, self.answer_counter, self.registration_time, self.last_answer_time)
@@ -74,17 +81,20 @@ class answer_set (db.Model):
 class background_question_answer(db.Model):
     __tablename__ = "background_question_answer"
     idbackground_question_answer = db.Column(db.Integer, primary_key=True)
-    answer_set_idanswer_set = db.Column(db.Integer, db.ForeignKey('answer_set.idanswer_set'))
+    answer_set_idanswer_set = db.Column(
+        db.Integer, db.ForeignKey('answer_set.idanswer_set'))
     answer = db.Column(db.String(120))
-    background_question_idbackground_question = db.Column(db.Integer, db.ForeignKey('background_question.idbackground_question'))
+    background_question_idbackground_question = db.Column(
+        db.Integer, db.ForeignKey('background_question.idbackground_question'))
 
     def __repr__(self):
-        return "<idbackground_question_answer = '%s', answer_set_idanswer_set = '%s', answer = '%s', background_question_idbackground_question = '%s'>" % (self.idbackground_question_answer, self.answer_set_idanswer_set, self.answer, self.background_question_idbackground_question) 
+        return "<idbackground_question_answer = '%s', answer_set_idanswer_set = '%s', answer = '%s', background_question_idbackground_question = '%s'>" % (self.idbackground_question_answer, self.answer_set_idanswer_set, self.answer, self.background_question_idbackground_question)
 
 
 def background_question_answer_query():
     return background_question_answer.query
 
+
 """
 class ChoiceForm(FlaskForm):
     opts = QuerySelectField(query_factory=background_question_answer_query, allow_blank=True)
@@ -98,30 +108,33 @@ vastaukset = u.answers.all()
 class question (db.Model):
     __tablename__ = "question"
     idquestion = db.Column(db.Integer, primary_key=True)
-    experiment_idexperiment = db.Column(db.Integer, db.ForeignKey('experiment.idexperiment'))
+    experiment_idexperiment = db.Column(
+        db.Integer, db.ForeignKey('experiment.idexperiment'))
     question = db.Column(db.String(120))
     left = db.Column(db.String(120))
     right = db.Column(db.String(120))
 
     def __repr__(self):
-        return "<idquestion = '%s', experiment_idexperiment = '%s', question = '%s', left = '%s', right = '%s'>" % (self.idquestion, self.experiment_idexperiment, self.question, self.left, self.right) 
+        return "<idquestion = '%s', experiment_idexperiment = '%s', question = '%s', left = '%s', right = '%s'>" % (self.idquestion, self.experiment_idexperiment, self.question, self.left, self.right)
 
 
 class embody_question (db.Model):
     __tablename__ = "embody_question"
     idembody = db.Column(db.Integer, primary_key=True)
-    experiment_idexperiment = db.Column(db.Integer, db.ForeignKey('experiment.idexperiment'))
+    experiment_idexperiment = db.Column(
+        db.Integer, db.ForeignKey('experiment.idexperiment'))
     picture = db.Column(db.Text)
     question = db.Column(db.Text)
 
     def __repr__(self):
-        return "<idembody = '%s', experiment_idexperiment = '%s', picture = '%s', question = '%s'>" % (self.idembody, self.experiment_idexperiment, self.picture, self.question )
+        return "<idembody = '%s', experiment_idexperiment = '%s', picture = '%s', question = '%s'>" % (self.idembody, self.experiment_idexperiment, self.picture, self.question)
 
 
 class page (db.Model):
     __tablename__ = "page"
     idpage = db.Column(db.Integer, primary_key=True)
-    experiment_idexperiment = db.Column(db.Integer, db.ForeignKey('experiment.idexperiment'))
+    experiment_idexperiment = db.Column(
+        db.Integer, db.ForeignKey('experiment.idexperiment'))
     type = db.Column(db.String(120), index=True)
     text = db.Column(db.Text)
     media = db.Column(db.String(120), index=True)
@@ -133,11 +146,19 @@ class page (db.Model):
 class answer (db.Model):
     __tablename__ = "answer"
     idanswer = db.Column(db.Integer, primary_key=True)
-    question_idquestion = db.Column(db.Integer, db.ForeignKey('question.idquestion'))
-    answer_set_idanswer_set = db.Column(db.Integer, db.ForeignKey('answer_set.idanswer_set'))
+    question_idquestion = db.Column(
+        db.Integer, db.ForeignKey('question.idquestion'))
+    answer_set_idanswer_set = db.Column(
+        db.Integer, db.ForeignKey('answer_set.idanswer_set'))
     answer = db.Column(db.String(120))
     page_idpage = db.Column(db.Integer, db.ForeignKey('page.idpage'))
 
+    def question(self):
+        return int(self.question_idquestion)
+
+    def result(self):
+        return int(self.answer)
+
     def __repr__(self):
         return "<idanswer = '%s', question_idquestion = '%s', answer_set_idanswer_set = '%s', answer = '%s', page_idpage = '%s'>" % (self.idanswer, self.question_idquestion, self.answer_set_idanswer_set, self.answer, self.page_idpage)
 
@@ -145,11 +166,19 @@ class answer (db.Model):
 class embody_answer (db.Model):
     __tablename__ = "embody_answer"
     idanswer = db.Column(db.Integer, primary_key=True)
-    answer_set_idanswer_set = db.Column(db.Integer, db.ForeignKey('answer_set.idanswer_set'))
+    answer_set_idanswer_set = db.Column(
+        db.Integer, db.ForeignKey('answer_set.idanswer_set'))
     page_idpage = db.Column(db.Integer, db.ForeignKey('page.idpage'))
-    embody_question_idembody = db.Column(db.Integer, db.ForeignKey('embody_question.idembody'))
+    embody_question_idembody = db.Column(
+        db.Integer, db.ForeignKey('embody_question.idembody'))
     coordinates = db.Column(db.Text)
 
+    def question(self):
+        return self.embody_question_idembody
+
+    def result(self):
+        return self.coordinates
+
     def __repr__(self):
         return "<idanswer = '%s', answer_set_idanswer_set = '%s', coordinates = '%s', page_idpage = '%s', embody_question_idembody='%s' >" % (self.idanswer, self.answer_set_idanswer_set, self.coordinates, self.page_idpage, self.embody_question_idembody)
 
@@ -161,17 +190,24 @@ class trial_randomization (db.Model):
     randomized_idpage = db.Column(db.Integer)
     answer_set_idanswer_set = db.Column(db.Integer)
     experiment_idexperiment = db.Column(db.Integer)
-    
+
     def __repr__(self):
         return "<idtrial_randomization = '%s', page_idpage = '%s', randomized_idpage = '%s', answer_set_idanswer_set = '%s', experiment_idexperiment = '%s'>" % (self.idtrial_randomization, self.page_idpage, self.randomized_idpage, self.answer_set_idanswer_set, self.experiment_idexperiment)
 
+    @classmethod
+    def get_randomized_page(cls, page_id):
+        return cls.query.filter(and_(
+            cls.answer_set_idanswer_set == session['answer_set'],
+            cls.page_idpage == page_id)).first()
+
 
 class forced_id (db.Model):
     __tablename__ = "forced_id"
     idforced_id = db.Column(db.Integer, primary_key=True)
-    experiment_idexperiment = db.Column(db.Integer, db.ForeignKey('experiment.idexperiment'))
-    pregenerated_id = db.Column(db.String(120))     
-        
+    experiment_idexperiment = db.Column(
+        db.Integer, db.ForeignKey('experiment.idexperiment'))
+    pregenerated_id = db.Column(db.String(120))
+
     def __repr__(self):
         return "<idforced_id = '%s', experiment_idexperiment = '%s', pregenerated_id = '%s'>" % (self.idforced_id, self.experiment_idexperiment, self.pregenerated_id)
 
@@ -184,16 +220,15 @@ class user(UserMixin, db.Model):
     password_hash = db.Column(db.String(128))
 
     def __repr__(self):
-        return '<user {}>'.format(self.username) 
-    
+        return '<user {}>'.format(self.username)
+
     def set_password(self, password):
         self.password_hash = generate_password_hash(password)
 
     def check_password(self, password):
         return check_password_hash(self.password_hash, password)
 
-    
+
 @login.user_loader
 def load_user(id):
     return user.query.get(int(id))
-    
\ No newline at end of file
diff --git a/app/routes.py b/app/routes.py
index 342fcb19825935a067d22e8d145fe361c2a48a86..9d64ba14306b391f4546579683f54c383f1e1b8f 100644
--- a/app/routes.py
+++ b/app/routes.py
@@ -1,52 +1,41 @@
-
-
-import csv
 import os
 import random
 import secrets
 from datetime import datetime
-import tempfile
-import json
-
-from flask import (
-    Flask, 
-    render_template, 
-    request, 
-    session, 
-    flash, 
-    redirect, 
-    url_for, 
-    Blueprint,
-    send_file
-)
 
+from flask import (render_template,
+                   request,
+                   session,
+                   flash,
+                   redirect,
+                   url_for)
 from sqlalchemy import and_
 from flask_login import current_user, login_user, logout_user, login_required
-from flask_babel import Babel, _, lazy_gettext as _l
 
-from app import app, db, babel
+from app import app, db
 from app.models import background_question, experiment
 from app.models import background_question_answer
-from app.models import page, question, embody_question, embody_answer
+from app.models import page
 from app.models import background_question_option
-from app.models import answer_set, answer, forced_id
+from app.models import answer_set, forced_id
 from app.models import user, trial_randomization
 from app.forms import LoginForm, RegisterForm, StartWithIdForm
 
-#Stimuli upload folder setting
+# Stimuli upload folder setting
 APP_ROOT = os.path.dirname(os.path.abspath(__file__))
 
+
 @app.route('/')
 @app.route('/index')
 def index():
     experiments = experiment.query.all()
-    
+
     if session:
         flash("")
     else:
         #flash("sessio ei voimassa")
         session['language'] = "English"
-    
+
     return render_template('index.html', title='Home', experiments=experiments)
 
 
@@ -54,21 +43,21 @@ def index():
 def consent():
     exp_id = request.args.get('exp_id', None)
     experiment_info = experiment.query.filter_by(idexperiment=exp_id).first()
-    
+
     instruction_paragraphs = str(experiment_info.short_instruction)
     instruction_paragraphs = instruction_paragraphs.split('<br>')
-    
+
     consent_paragraphs = str(experiment_info.consent_text)
     consent_paragraphs = consent_paragraphs.split('<br>')
 
     if experiment_info.use_forced_id == 'On':
         return redirect(url_for('begin_with_id', exp_id=exp_id))
 
-    return render_template('consent.html', 
-                            exp_id=exp_id, 
-                            experiment_info=experiment_info, 
-                            instruction_paragraphs=instruction_paragraphs, 
-                            consent_paragraphs=consent_paragraphs)
+    return render_template('consent.html',
+                           exp_id=exp_id,
+                           experiment_info=experiment_info,
+                           instruction_paragraphs=instruction_paragraphs,
+                           consent_paragraphs=consent_paragraphs)
 
 
 @app.route('/set_language')
@@ -88,28 +77,28 @@ def remove_language():
 def participant_session():
     '''Set up session variables and create answer_set (database level sessions)'''
 
-    #start session
+    # start session
     session['exp_id'] = request.args.get('exp_id', None)
     session['agree'] = request.args.get('agree', None)
-  
-    #If user came via the route for "I have already a participant ID that I wish to use, Use that ID, otherwise generate a random ID
+
+    # If user came via the route for "I have already a participant ID that I wish to use, Use that ID, otherwise generate a random ID
     if 'begin_with_id' in session:
         session['user'] = session['begin_with_id']
         session.pop('begin_with_id', None)
     else:
-        #lets generate a random id. If the same id is allready in db, lets generate a new one and finally use that in session['user']
+        # lets generate a random id. If the same id is allready in db, lets generate a new one and finally use that in session['user']
         random_id = secrets.token_hex(3)
         check_id = answer_set.query.filter_by(session=random_id).first()
 
         while check_id is not None:
             random_id = secrets.token_hex(3)
             check_id = answer_set.query.filter_by(session=random_id).first()
-        
+
         session['user'] = random_id
 
     # Set session status variables
-    exp_status = experiment.query.filter_by(idexperiment=session['exp_id']).first()
-
+    exp_status = experiment.query.filter_by(
+        idexperiment=session['exp_id']).first()
 
     # Create answer set for the participant in the database
     the_time = datetime.now()
@@ -121,55 +110,57 @@ def participant_session():
         answer_set_type = 'embody'
 
     participant_answer_set = answer_set(experiment_idexperiment=session['exp_id'],
-                                        session=session['user'], 
-                                        agreement = session['agree'], 
-                                        answer_counter = '0', 
-                                        answer_type = answer_set_type, 
-                                        registration_time=the_time, 
+                                        session=session['user'],
+                                        agreement=session['agree'],
+                                        answer_counter='0',
+                                        answer_type=answer_set_type,
+                                        registration_time=the_time,
                                         last_answer_time=the_time)
     db.session.add(participant_answer_set)
     db.session.commit()
 
-    
-    #If trial randomization is set to 'On' for the experiment, create a randomized trial order for this participant
-    #identification is based on the uniquie answer set id
+    # If trial randomization is set to 'On' for the experiment, create a randomized trial order for this participant
+    # identification is based on the uniquie answer set id
     if exp_status.randomization == 'On':
-    
+
         session['randomization'] = 'On'
-        
-        #create a list of page id:s for the experiment
-        experiment_pages = page.query.filter_by(experiment_idexperiment=session['exp_id']).all()
+
+        # create a list of page id:s for the experiment
+        experiment_pages = page.query.filter_by(
+            experiment_idexperiment=session['exp_id']).all()
         original_id_order_list = [(int(o.idpage)) for o in experiment_pages]
-        
-        #create a randomized page id list    
-        helper_list = original_id_order_list 
+
+        # create a randomized page id list
+        helper_list = original_id_order_list
         randomized_order_list = []
-    
+
         for i in range(len(helper_list)):
             element = random.choice(helper_list)
             helper_list.remove(element)
             randomized_order_list.append(element)
-        
-        #Input values into trial_randomization table where the original page_ids have a corresponding randomized counterpart
-        experiment_pages = page.query.filter_by(experiment_idexperiment=session['exp_id']).all()
+
+        # Input values into trial_randomization table where the original page_ids have a corresponding randomized counterpart
+        experiment_pages = page.query.filter_by(
+            experiment_idexperiment=session['exp_id']).all()
         original_id_order_list = [(int(o.idpage)) for o in experiment_pages]
-        
+
         for c in range(len(original_id_order_list)):
-            random_page = trial_randomization(page_idpage=original_id_order_list[c], randomized_idpage=randomized_order_list[c], answer_set_idanswer_set = participant_answer_set.idanswer_set, experiment_idexperiment = session['exp_id'])
+            random_page = trial_randomization(page_idpage=original_id_order_list[c], randomized_idpage=randomized_order_list[
+                                              c], answer_set_idanswer_set=participant_answer_set.idanswer_set, experiment_idexperiment=session['exp_id'])
             db.session.add(random_page)
             db.session.commit()
-    
+
     if exp_status.randomization == "Off":
         session['randomization'] = "Off"
 
-    #store participants session id in session list as answer_set, based on experiment id and session id
-    session_id_for_participant = answer_set.query.filter(and_(answer_set.session==session['user'], answer_set.experiment_idexperiment==session['exp_id'])).first()
+    # store participants session id in session list as answer_set, based on experiment id and session id
+    session_id_for_participant = answer_set.query.filter(and_(
+        answer_set.session == session['user'], answer_set.experiment_idexperiment == session['exp_id'])).first()
     session['answer_set'] = session_id_for_participant.idanswer_set
-
-
-    #collect experiments mediatype from db to session['type']. 
-    #This is later used in task.html to determine page layout based on stimulus type
-    mediatype = page.query.filter_by(experiment_idexperiment=session['exp_id']).first()
+    # collect experiments mediatype from db to session['type'].
+    # This is later used in task.html to determine page layout based on stimulus type
+    mediatype = page.query.filter_by(
+        experiment_idexperiment=session['exp_id']).first()
     if mediatype:
         session['type'] = mediatype.type
     else:
@@ -180,124 +171,129 @@ def participant_session():
     if 'user' in session:
         user = session['user']
         return redirect('/register')
-      
+
     return "Session start failed return <a href = '/login'></b>" + "Home</b></a>"
 
 
 @app.route('/register', methods=['GET', 'POST'])
 def register():
-    
+
     form = RegisterForm(request.form)
     questions_and_options = {}
-    questions = background_question.query.filter_by(experiment_idexperiment=session['exp_id']).all()
-
-    for q in questions:    
-    
-        options = background_question_option.query.filter_by(background_question_idbackground_question=q.idbackground_question).all()
-        options_list = [(o.option, o.idbackground_question_option) for o in options]
-        questions_and_options[q.idbackground_question, q.background_question]  = options_list
-    
- 
+    questions = background_question.query.filter_by(
+        experiment_idexperiment=session['exp_id']).all()
+
+    for q in questions:
+
+        options = background_question_option.query.filter_by(
+            background_question_idbackground_question=q.idbackground_question).all()
+        options_list = [(o.option, o.idbackground_question_option)
+                        for o in options]
+        questions_and_options[q.idbackground_question,
+                              q.background_question] = options_list
+
     form.questions1 = questions_and_options
-    
-    if request.method == 'POST'and form.validate():
-    
+
+    if request.method == 'POST' and form.validate():
+
         data = request.form.to_dict()
         for key, value in data.items():
 
-            
-            #tähän db insertit
+            # tähän db insertit
 
-            #flash(key)
-            #flash(value)
-            #Input registration page answers to database
-            participant_background_question_answers = background_question_answer(answer_set_idanswer_set=session['answer_set'], answer=value, background_question_idbackground_question=key)
+            # flash(key)
+            # flash(value)
+            # Input registration page answers to database
+            participant_background_question_answers = background_question_answer(
+                answer_set_idanswer_set=session['answer_set'], answer=value, background_question_idbackground_question=key)
             db.session.add(participant_background_question_answers)
             db.session.commit()
 
         return redirect('/instructions')
-   
-    
-    return render_template('register.html', form=form)
 
+    return render_template('register.html', form=form)
 
 
 @app.route('/begin_with_id', methods=['GET', 'POST'])
 def begin_with_id():
     '''Begin experiment with experiment ID. GET -method returns login page for starting the 
     experiment and POST -method verifys users ID before starting new experiment'''
-    
+
     exp_id = request.args.get('exp_id', None)
     form = StartWithIdForm()
     experiment_info = experiment.query.filter_by(idexperiment=exp_id).first()
-    
+
     instruction_paragraphs = str(experiment_info.short_instruction)
     instruction_paragraphs = instruction_paragraphs.split('<br>')
-    
+
     consent_paragraphs = str(experiment_info.consent_text)
     consent_paragraphs = consent_paragraphs.split('<br>')
 
     if form.validate_on_submit():
-        
+
         variable = form.participant_id.data
-        
-        #check if participant ID is found from db with this particular ID. If a match is found inform about error
-        participant = answer_set.query.filter(and_(answer_set.session==variable, answer_set.experiment_idexperiment==exp_id)).first()
-        is_id_valid = forced_id.query.filter(and_(forced_id.pregenerated_id==variable, forced_id.experiment_idexperiment==exp_id)).first()
-        
+
+        # check if participant ID is found from db with this particular ID. If a match is found inform about error
+        participant = answer_set.query.filter(and_(
+            answer_set.session == variable, answer_set.experiment_idexperiment == exp_id)).first()
+        is_id_valid = forced_id.query.filter(and_(
+            forced_id.pregenerated_id == variable, forced_id.experiment_idexperiment == exp_id)).first()
+
         if participant is not None:
             flash(_('ID already in use'))
-            return redirect(url_for('begin_with_id', exp_id=exp_id))        
-        
-        #if there was not a participant already in DB:
+            return redirect(url_for('begin_with_id', exp_id=exp_id))
+
+        # if there was not a participant already in DB:
         if participant is None:
-    
+
             if is_id_valid is None:
                 flash(_('No such ID set for this experiment'))
-                return redirect(url_for('begin_with_id', exp_id=exp_id))        
+                return redirect(url_for('begin_with_id', exp_id=exp_id))
 
             else:
-                #save the participant ID in session list for now, this is deleted after the session has been started in participant_session-view
+                # save the participant ID in session list for now, this is deleted after the session has been started in participant_session-view
                 session['begin_with_id'] = form.participant_id.data
                 return render_template('consent.html', exp_id=exp_id, experiment_info=experiment_info, instruction_paragraphs=instruction_paragraphs, consent_paragraphs=consent_paragraphs)
-        
+
     return render_template('begin_with_id.html', exp_id=exp_id, form=form)
 
 
 @app.route('/admin_dryrun', methods=['GET', 'POST'])
 @login_required
 def admin_dryrun():
-    
+
     exp_id = request.args.get('exp_id', None)
     form = StartWithIdForm()
     experiment_info = experiment.query.filter_by(idexperiment=exp_id).first()
 
     if form.validate_on_submit():
-        
-        #check if participant ID is found from db with this particular ID. If a match is found inform about error
-        participant = answer_set.query.filter(and_(answer_set.session==form.participant_id.data, answer_set.experiment_idexperiment==exp_id)).first()
+
+        # check if participant ID is found from db with this particular ID. If a match is found inform about error
+        participant = answer_set.query.filter(and_(
+            answer_set.session == form.participant_id.data, answer_set.experiment_idexperiment == exp_id)).first()
         if participant is not None:
             flash('ID already in use')
-            return redirect(url_for('admin_dryrun', exp_id=exp_id))        
-        
-        #if there was not a participant already in DB:
+            return redirect(url_for('admin_dryrun', exp_id=exp_id))
+
+        # if there was not a participant already in DB:
         if participant is None:
-            #save the participant ID in session list for now, this is deleted after the session has been started in participant_session-view
+            # save the participant ID in session list for now, this is deleted after the session has been started in participant_session-view
             session['begin_with_id'] = form.participant_id.data
             return render_template('consent.html', exp_id=exp_id, experiment_info=experiment_info)
-        
+
     return render_template('admin_dryrun.html', exp_id=exp_id, form=form)
 
 
 @app.route('/instructions')
 def instructions():
-    
+
     participant_id = session['user']
-    instructions = experiment.query.filter_by(idexperiment = session['exp_id']).first()
-    
+    instructions = experiment.query.filter_by(
+        idexperiment=session['exp_id']).first()
+
     instruction_paragraphs = str(instructions.instruction)
     instruction_paragraphs = instruction_paragraphs.split('<br>')
-    
+
     return render_template('instructions.html', instruction_paragraphs=instruction_paragraphs, participant_id=participant_id)
 
 
@@ -308,13 +304,14 @@ def login():
         return redirect(url_for('index'))
     form = LoginForm()
     if form.validate_on_submit():
-        user_details = user.query.filter_by(username=form.username.data).first()
+        user_details = user.query.filter_by(
+            username=form.username.data).first()
         if user_details is None or not user_details.check_password(form.password.data):
             flash('Invalid username or password')
             return redirect(url_for('login'))
-        login_user(user_details, remember=form.remember_me.data)    
+        login_user(user_details, remember=form.remember_me.data)
         return redirect(url_for('index'))
-    
+
 #        flash('Login requested for user {}, remember_me={}'.format(
 #            form.username.data, form.remember_me.data))
 #        return redirect('/index')
@@ -329,129 +326,15 @@ def logout():
 
 @app.route('/view_research_notification')
 def view_research_notification():
-    
+
     exp_id = request.args.get('exp_id', None)
     image = experiment.query.filter_by(idexperiment=exp_id).first()
     research_notification_filename = image.research_notification_filename
-    
-    return render_template('view_research_notification.html', research_notification_filename=research_notification_filename)
-
-
-@app.route('/download_csv')
-@login_required
-def download_csv():
-
-    exp_id = request.args.get('exp_id', None)
-    experiment_info = experiment.query.filter_by(idexperiment = exp_id).all()
-
-    # answer sets with participant ids
-    participants = answer_set.query.filter_by(experiment_idexperiment= exp_id).all()
-
-    # pages aka stimulants
-    pages = page.query.filter_by(experiment_idexperiment=exp_id).all()
-
-    # background questions
-    bg_questions = background_question.query.filter_by(
-        experiment_idexperiment=exp_id).all()
-
-    # question
-    questions = question.query.filter_by(experiment_idexperiment=exp_id).all()
-
-    # embody questions
-    embody_questions = embody_question.query.filter_by(experiment_idexperiment=exp_id).all()
-
-    #started and finished ratings counters
-    started_ratings = answer_set.query.filter_by(
-        experiment_idexperiment=exp_id).count()
-    experiment_page_count = page.query.filter_by(
-        experiment_idexperiment=exp_id).count()
-    finished_ratings = answer_set.query.filter(and_(
-        answer_set.answer_counter == experiment_page_count, answer_set.experiment_idexperiment == exp_id)).count()
-
-    csv = ''
-
-    # create CSV-header
-    header = 'participant id;'
-    header += ';'.join([str(count) +'. bg_question: '+ question.background_question.strip() for count,question in enumerate(bg_questions, 1)])
 
-    for idx in range(1,len(pages) + 1):
-        if len(questions) > 0:
-            header += ';' + ';'.join(['page' + str(idx) + '_' + str(count) +'. slider_question: ' + question.question.strip() for count,question in enumerate(questions, 1)]) 
-    for idx in range(1,len(pages) + 1):
-        if len(embody_questions) > 0:
-            header += ';' + ';'.join(['page' + str(idx) + '_' + str(count) +'. embody_question: '+ question.picture.strip() for count,question in enumerate(embody_questions, 1)])
-
-    csv += header + '\r\n'
-    answer_row = ''
-
-    for participant in participants:
-
-        # list only finished answer sets
-        if experiment_page_count == participant.answer_counter:
-
-            try:
-
-                # append user session id
-                answer_row += participant.session + ';'
-
-                # append background question answers
-                bg_answers = background_question_answer.query.filter_by(answer_set_idanswer_set=participant.idanswer_set).all()
-                bg_answers_list = [ str(a.answer).strip() for a in bg_answers]
-                answer_row += ';'.join(bg_answers_list) + ';'
-
-                # append slider answers 
-                slider_answers = answer.query.filter_by(answer_set_idanswer_set=participant.idanswer_set).all()     
-                answers_list = [ str(a.answer).strip() for a in slider_answers]    
-                answer_row += ';'.join(answers_list) + ';' if slider_answers else len(questions) * len(pages) * ';'
-
-                # append embody answers (coordinates)
-                # save embody answers as bitmap images  
-                embody_answers = embody_answer.query.filter_by(answer_set_idanswer_set=participant.idanswer_set).all()     
-                answers_list = []
-                for embody_answer_data in embody_answers:
-
-                    try:
-                        embody_answer_data = json.loads(embody_answer_data.coordinates)
-                        coordinates_to_bitmap = [[0 for x in range(embody_answer_data['height'] + 2)] for y in range(embody_answer_data['width'] + 2)] 
-
-                        for point in list(zip( embody_answer_data['x'], embody_answer_data['y'] )):
-
-                            try:
-                                 coordinates_to_bitmap[point[0]][point[1]] += 0.1
-                            except IndexError:
-                                 continue
-
-                        answers_list.append(json.dumps(coordinates_to_bitmap))
-  
-                    except ValueError as err:
-                        app.logger.info(err)
-
-                # old way to save only visited points:
-                # answers_list = [ json.dumps(list(zip( json.loads(a.coordinates)['x'], json.loads(a.coordinates)['y']))) for a in embody_answers]    
-                answer_row += ';'.join(answers_list) if embody_answers else len(embody_questions) * len(pages) * ';'
-
-            except TypeError as err:
-                print(err)
-
-            csv += answer_row + '\r\n'
-            answer_row = ''
-
-    try:
-        fd, path = tempfile.mkstemp()
-        with os.fdopen(fd, 'w') as tmp:
-            tmp.write(csv)
-            tmp.flush()
-
-            return send_file(path, mimetype='text/csv')
+    return render_template('view_research_notification.html', research_notification_filename=research_notification_filename)
 
-    finally:
-        os.remove(path)
-    
 
 @app.route('/researcher_info')
 @login_required
 def researcher_info():
     return render_template('researcher_info.html')
-
-
-# EOF
diff --git a/app/static/css/custom.css b/app/static/css/custom.css
deleted file mode 100644
index 407f953044adc21af3f22e7b7565a6e864acf6ee..0000000000000000000000000000000000000000
--- a/app/static/css/custom.css
+++ /dev/null
@@ -1,5 +0,0 @@
-body{
-
-background-color: #000000;
-
-}
\ No newline at end of file
diff --git a/app/static/css/main.css b/app/static/css/main.css
index eeb85710389fc8101bdd970f30d073288b47bf9d..54685cd436f2eaed95f6280edc034423d1500284 100644
--- a/app/static/css/main.css
+++ b/app/static/css/main.css
@@ -75,4 +75,20 @@ body {
         max-width: 90%;
     }
   
-}
\ No newline at end of file
+}
+
+.stimulus img {
+    height: 100%;
+    object-fit: contain;
+}
+
+
+#export-link-container {
+    margin-top: 20px;
+    padding: 10px;
+}
+
+#export-error {
+    float:right;
+    color:red;
+}
diff --git a/app/static/js/canvas.js b/app/static/js/canvas.js
index 0f8bf30e0a0979f4382582f4b61dd213899dc957..b456b80e5315f4334ca192bf0d6ad8c5617c8a01 100644
--- a/app/static/js/canvas.js
+++ b/app/static/js/canvas.js
@@ -59,8 +59,14 @@ $(document).ready(function() {
 
     // Click handlers
     canvas.mousedown(function(e){
-        var mouseX = e.pageX - this.offsetLeft;
-        var mouseY = e.pageY - this.offsetTop;
+
+        //var mouseX = e.pageX - this.offsetLeft;
+        //var mouseY = e.pageY - this.offsetTop;
+
+        var parentOffset = $(this).offset(); 
+        var mouseX = e.pageX - parentOffset.left;
+        var mouseY = e.pageY - parentOffset.top;
+
         paint = true;
 
         if (pointInsideBaseImage([mouseX, mouseY])) {
@@ -70,8 +76,10 @@ $(document).ready(function() {
     });
 
     canvas.mousemove(function(e){
-        var mouseX = e.pageX - this.offsetLeft;
-        var mouseY = e.pageY - this.offsetTop;
+
+        var parentOffset = $(this).offset(); 
+        var mouseX = e.pageX - parentOffset.left;
+        var mouseY = e.pageY - parentOffset.top;
 
         if (paint && pointInsideBaseImage([mouseX, mouseY])){
             addClick(mouseX, mouseY, true);
@@ -81,8 +89,13 @@ $(document).ready(function() {
 
     canvas.bind('touchmove', function(e){
         e.preventDefault()
-        var mouseX = e.touches[0].pageX - this.offsetLeft;
-        var mouseY = e.touches[0].pageY - this.offsetTop;
+
+        //var mouseX = e.touches[0].pageX - this.offsetLeft;
+        //var mouseY = e.touches[0].pageY - this.offsetTop;
+
+        var parentOffset = $(this).offset(); 
+        var mouseX = e.touches[0].pageX - parentOffset.left;
+        var mouseY = e.touches[0].pageY - parentOffset.top;
 
         [mouseX, mouseY] = scaleClickCoordinates($(this)[0], mouseX, mouseY)
 
@@ -94,8 +107,11 @@ $(document).ready(function() {
 
     canvas.bind('touchstart', function(e){
         e.preventDefault()
-        var mouseX = e.touches[0].pageX - this.offsetLeft;
-        var mouseY = e.touches[0].pageY - this.offsetTop;
+
+        var parentOffset = $(this).offset(); 
+        var mouseX = e.touches[0].pageX - parentOffset.left;
+        var mouseY = e.touches[0].pageY - parentOffset.top;
+
         paint = true;
 
         [mouseX, mouseY] = scaleClickCoordinates($(this)[0], mouseX, mouseY)
diff --git a/app/static/js/getCSV.js b/app/static/js/getCSV.js
new file mode 100644
index 0000000000000000000000000000000000000000..4b0313bbc2f96e59041548022644136f886ac246
--- /dev/null
+++ b/app/static/js/getCSV.js
@@ -0,0 +1,77 @@
+
+
+
+$(document).ready(function()  {
+
+    var exportButton = $(".get-csv-results");
+
+    var progressBarContainer = $(".progress")
+    var progressBar = $("#export-results-bar")
+
+    var exportLinkContainer = $("#export-link-container");
+    var exportLink = $("#export-link");
+    var exportError = $("#export-error");
+
+    // With sockets 
+    function initConnection(socket) {
+
+        socket.on('success', function(msg) {
+            exportButton.text('Generating file...')
+            exportButton.addClass('disabled')
+        });
+
+        socket.on('progress', function(data) {
+            progressBar.width(100*(data.done/data.from) + '%')
+        });
+
+        socket.on('timeout', function(data) {
+            // kill connection
+
+            socket.emit('end')
+            socket.disconnect()            
+
+            exportButton.text('Export results')
+            exportButton.removeClass('disabled')
+            progressBarContainer.addClass("hidden")
+
+            // show error
+            exportLinkContainer.removeClass("hidden")
+            exportError.text('Error: ' + data.exc)
+        });
+
+        socket.on('file_ready', function(file) {
+
+            socket.emit('end')
+            socket.disconnect()            
+
+            exportButton.text('File is ready!')
+
+            // show link
+            exportLinkContainer.removeClass("hidden")
+            exportLink.text('Download: ' + file.filename + '.csv')
+
+            // set filename to exportlink
+            var href = exportLink.attr('href');
+            href += '&path=' + file.path
+            $(exportLink).attr('href', href);
+
+            // Remove progress bar
+            progressBarContainer.addClass("hidden")
+            progressBar.width('0%')
+        });
+    }
+
+
+    exportButton.click(function(event) {
+        event.preventDefault()
+
+        // Init socket
+        var socket = io.connect(exportURL);
+        initConnection(socket)
+
+        // start generating csv file...
+        socket.emit('generate_csv', {exp_id: this.dataset.value})
+
+        progressBarContainer.removeClass("hidden")
+    })
+})
diff --git a/app/static/js/getDrawing.js b/app/static/js/getDrawing.js
index ed1a11d87e2dd09ac7b5760f1953ef3da571a88b..70db2ae5142c7cda61360b362877a3b2d410a20d 100644
--- a/app/static/js/getDrawing.js
+++ b/app/static/js/getDrawing.js
@@ -1,8 +1,5 @@
 
 
-const baseURI = 'http://onni.utu.fi/';
-var getDrawingURI = baseURI + 'create_embody';
-
 $(document).ready(function()  {
 
     var drawButtons = $(".embody-get-drawing");
@@ -22,7 +19,8 @@ $(document).ready(function()  {
         });
 
         socket.on('end', function(img) {
-
+            // kill connection
+            socket.emit('end')
             socket.disconnect()            
 
             // Draw image to statistic -page
@@ -44,32 +42,13 @@ $(document).ready(function()  {
         var socket = io.connect(getDrawingURI);
         initConnection(socket)
 
-        // 
         var pageId = this.dataset.value.split('-')[0]
         var embodyId = this.dataset.value.split('-')[1]
+
         socket.emit('draw', {page:pageId, embody:embodyId})
         progressBarContainer.removeClass("hidden")
 
         scrollTo('plotted-image')
-
-        /*
-        With AJAX -calls
-        var spinner = $(event.target.firstElementChild)
-        spinner.removeClass("hidden")
-
-        $.ajax({
-            url: getDrawingURI,
-            method: 'POST',
-            data: {page:pageId}
-        }).done(function(data) {
-            var source = JSON.parse(data).path;
-            console.log(source)
-            d = new Date()
-            imageContainer.attr("src", "/static/" + source + "?" +d.getTime())
-            spinner.addClass("hidden")
-        })
-        */
-
     })
 
     function scrollTo(hash) {
@@ -77,5 +56,4 @@ $(document).ready(function()  {
             'scrollTop':   $('#'+hash).offset().top - 250
         }, 500);
     }
-
 })
diff --git a/app/static/js/urls.js b/app/static/js/urls.js
new file mode 100644
index 0000000000000000000000000000000000000000..d2349e01d48a4087afe894ff7d0d22e1c849c4de
--- /dev/null
+++ b/app/static/js/urls.js
@@ -0,0 +1,6 @@
+
+const baseURI = 'localhost/';
+//const baseURI = 'http://onni.utu.fi/';
+
+var exportURL = baseURI + 'download_csv';
+var getDrawingURI = baseURI + 'create_embody';
\ No newline at end of file
diff --git a/app/task/templates/task.html b/app/task/templates/task.html
index 6e322ac105947dda8ac4796a6fa3e6fcf0b086b9..62f57d7179c6925d48aba68eb49bbe51d3ca6e53 100644
--- a/app/task/templates/task.html
+++ b/app/task/templates/task.html
@@ -1,117 +1,136 @@
 {% extends "base.html" %}
 {% block content %}
 
-<br>    
 
-{% if session['randomization']=='Off' %}
+<div class="container stimulus">
+    <div class="row justify-content-center">
 
-    {% if session['type']=='text' %}
+        {% if session['randomization']=='Off' %}
+
+        {% if session['type']=='text' %}
         <div class="container text-center mt-5 pt-5">
             {% for page in pages.items %}
             <h{{ stimulus_size_text }} class="text-center mt-5"><br>{{ page.text }}</h{{ stimulus_size_text }}>
             {% endfor %}
         </div>
-           <br><br>
-    {% endif %}
-    
-    {% if session['type']=='picture' %}
-        <div class="container stimulus col-{{stimulus_size}} mt-5 pt-5">
+        <br><br>
+        {% endif %}
+
+        {% if session['type']=='picture' %}
+        <div class="stimulus col-{{stimulus_size}} mt-5 pt-5">
             {% for page in pages.items %}
-                <img src="/{{ page.media }}" class="img-fluid">
+            <img src="/{{ page.media }}" class="img-fluid">
             {% endfor %}
         </div>
-    {% endif %}
-    
-    {% if session['type']=='video' %}
+        {% endif %}
+
+        {% if session['type']=='video' %}
         <div class="col-{{stimulus_size}} container stimulus mt-5 pt-5">
             {% for page in pages.items %}
-                <div class="embed-responsive embed-responsive-16by9">
-                    <iframe class="embed-responsive-item" src="/{{ page.media }}" allowfullscreen></iframe>
-                </div>
+            <div class="embed-responsive embed-responsive-16by9">
+                <iframe class="embed-responsive-item" src="/{{ page.media }}" allowfullscreen></iframe>
+            </div>
             {% endfor %}
         </div>
-    {% endif %}
+        {% endif %}
 
-    {% if session['type']=='audio' %}
+        {% if session['type']=='audio' %}
         <div class="col-{{stimulus_size}} container stimulus mt-5 pt-5">
             {% for page in pages.items %}
-                <div class="embed-responsive embed-responsive-16by9">
-                    <iframe class="embed-responsive-item" src="/{{ page.media }}" allowfullscreen></iframe>
-                </div>
+            <div class="embed-responsive embed-responsive-16by9">
+                <audio class="embed-responsive-item" controls autoplay>
+                    <source src="/{{ page.media }}">
+                    Your browser does not support the audio element.
+                </audio>
+            </div>
             {% endfor %}
         </div>
-    {% endif %}
+        {% endif %}
 
-{% else %}
+        {% else %}
 
-    {% if session['type']=='text' %}
+        {% if session['type']=='text' %}
         <div class="container text-center mt-5 pt-5">
             {% for page in pages.items %}
-            <h{{ stimulus_size_text }} class="text-center mt-5"><br>{{ randomized_stimulus.text }}</h{{ stimulus_size_text }}>
+            <h{{ stimulus_size_text }} class="text-center mt-5"><br>{{ randomized_stimulus.text }}
+            </h{{ stimulus_size_text }}>
             {% endfor %}
         </div>
-           <br><br>
-    {% endif %}
-    
-    {% if session['type']=='picture' %}
-        <div class="container stimulus col-{{stimulus_size}} mt-5 pt-5">
+        <br><br>
+        {% endif %}
+
+        {% if session['type']=='picture' %}
+        <div class="stimulus col-{{stimulus_size}} mt-5 pt-5">
             {% for page in pages.items %}
-                <img src="/{{ randomized_stimulus.media }}" class="img-fluid">
+            <img src="/{{ randomized_stimulus.media }}" class="img-fluid">
             {% endfor %}
+
+
         </div>
-    {% endif %}
-    
-    {% if session['type']=='video' %}
+        {% endif %}
+
+        {% if session['type']=='video' %}
         <div class="col-{{stimulus_size}} container stimulus mt-5 pt-5">
             {% for page in pages.items %}
-                <div class="embed-responsive embed-responsive-16by9">
-                    <iframe class="embed-responsive-item" src="/{{ randomized_stimulus.media }}" allowfullscreen></iframe>
-                </div>
+            <div class="embed-responsive embed-responsive-16by9">
+                <iframe class="embed-responsive-item" src="/{{ randomized_stimulus.media }}" allowfullscreen></iframe>
+            </div>
             {% endfor %}
         </div>
-    {% endif %}
+        {% endif %}
 
-    {% if session['type']=='audio' %}
+        {% if session['type']=='audio' %}
         <div class="col-{{stimulus_size}} container stimulus mt-5 pt-5">
             {% for page in pages.items %}
-                <div class="embed-responsive embed-responsive-16by9">
-                    <iframe class="embed-responsive-item" src="/{{ randomized_stimulus.media }}" allowfullscreen></iframe>
-                </div>
+            <div class="embed-responsive embed-responsive-16by9">
+                <audio class="embed-responsive-item" controls autoplay>
+                    <source src="/{{ randomized_stimulus.media }}">
+                    Your browser does not support the audio element.
+                </audio>
+            </div>
             {% endfor %}
         </div>
-    {% endif %}
+        {% endif %}
 
-{% endif %}
-    
-<br>
- 
- <h4 class="text-center">{{ rating_instruction }}</h4>
+        {% endif %}
 
- <!-- Select form type -->
+        {% if form.__name__ == 'embody' %}
+        <div class="col-4 mt-5 pt-5">
+            <div class="canvas-container">
+                <span class="canvas-info"></span>
+                <canvas id="embody-canvas" class="crosshair" width="20" height="20"></canvas>
+            </div>
 
+            {% for embody_question in embody_questions %}
+            <img id="idembody-{{embody_question.idembody}}"
+                class="embody-image {% if loop.last %}last-embody{% endif %} {% if loop.index != 1 %}hidden{% else %}selected-embody{% endif %}"
+                src={{ embody_question.picture }} />
+            {% endfor %}
 
- {% if form.__name__ == 'embody' %}
+            {% for embody_question in embody_questions %}
+            <p id="idquestion-{{ embody_question.idembody }}"
+                class="embody-question {% if loop.index != 1 %}hidden{% else %}selected-embody{% endif %}">
+                {{ embody_question.question }}</p>
+            {% endfor %}
 
-  <div class="canvas-container">
-  <span class="canvas-info"></span>
-  <canvas id="embody-canvas" class="crosshair" width="20" height="20" style="border: 1px solid blue;"  ></canvas>
+            <img id="baseImageMask" class="hidden" src={{ url_for('static', filename='img/dummy_600_mask.png') }} />
 
-  </div>
+        </div>
+        {% endif %}
 
+    </div>
+</div>
 
-  {% for embody_question in embody_questions %}
-        <img id="idembody-{{embody_question.idembody}}" class="embody-image {% if loop.last %}last-embody{% endif %} {% if loop.index != 1 %}hidden{% else %}selected-embody{% endif %}" src={{ embody_question.picture }} />
-  {% endfor %}
-  {% for embody_question in embody_questions %}
-        <p id="idquestion-{{ embody_question.idembody }}" class="embody-question {% if loop.index != 1 %}hidden{% else %}selected-embody{% endif %}"> {{ embody_question.question }}</p>
-  {% endfor %}
+<br>
+<h4 class="text-center">{{ rating_instruction }}</h4>
 
-  <img id="baseImageMask" class="hidden" src={{ url_for('static', filename='img/dummy_600_mask.png') }} />
+<!-- Select form type -->
 
+{% if form.__name__ == 'embody' %}
 
 
 
-  <form id="canvas-form" class="form-group mt-5" action="/task/embody/{{ page_num }}" method="post" >
+<form id="canvas-form" class="form-group mt-5" action="/task/embody/{{ page_num }}" method="post">
 
     <input id="canvas-data" type="hidden" value="" name="coordinates">
 
@@ -131,29 +150,29 @@
             <p>{{ _("Reload the page if canvas didn't appear") }} </p>
             <p>{{ _('You can zoom in/out the page view by pressing ctrl+/ctrl- (Windows) or ⌘+/⌘- (Mac)') }} </p>
         </div>
-    </div>    
-  </form>  
+    </div>
+</form>
 
-<script src="{{ url_for('static', filename='js/canvas.js') }}" ></script>
+<script src="{{ url_for('static', filename='js/canvas.js') }}"></script>
 
 {% elif form.__name__ == 'slider' %}
 
-  <form class="form-group mt-5" action="/task/question/{{ page_num }}" method="post">
+<form class="form-group mt-5" action="/task/question/{{ page_num }}" method="post">
     {% for category in form.categories1 %}
-        {% for scale in form.categories1[category] %}
-            <div class="row form-group mt-0 mb-0">
-                <h6 class="col-3 text-right mt-0 mb-0">
-                    {{ scale[0] }} 
-                </h6>
-                <h6 class="col text-center mt-0 mb-0">
-                    <label for="customRange">{{ category[1] }}</label>
-                    <input type="range" class="custom-range" id="customRange" name={{ category[0] }}>
-                </h6>
-                <h6 class="col-3 text-left mt-0 mb-0">
-                {{ scale[1] }}
-                </h6>
-            </div>
-         {% endfor %}   
+    {% for scale in form.categories1[category] %}
+    <div class="row form-group mt-0 mb-0">
+        <h6 class="col-3 text-right mt-0 mb-0">
+            {{ scale[0] }}
+        </h6>
+        <h6 class="col text-center mt-0 mb-0">
+            <label for="customRange">{{ category[1] }}</label>
+            <input type="range" class="custom-range" id="customRange" name={{ category[0] }}>
+        </h6>
+        <h6 class="col-3 text-left mt-0 mb-0">
+            {{ scale[1] }}
+        </h6>
+    </div>
+    {% endfor %}
     {% endfor %}
     <div class="form-row text-center">
         <div class="col-12">
@@ -164,9 +183,9 @@
             <br>
             <p>{{ _('You can zoom in/out the page view by pressing ctrl+/ctrl- (Windows) or ⌘+/⌘- (Mac)') }} </p>
         </div>
-    </div>    
-  </form>  
+    </div>
+</form>
 
 {% endif %}
-    
+
 {% endblock %}
\ No newline at end of file
diff --git a/app/task/views.py b/app/task/views.py
index e72588f910ab45bce172f12aa3904bfcb8d7bfb8..6e3a2085c9913f48963d4b4fa4b5e10eec24e036 100644
--- a/app/task/views.py
+++ b/app/task/views.py
@@ -37,13 +37,7 @@ task_blueprint = Blueprint("task", __name__,
 def get_randomized_page(page_id):
     """if trial randomization is on we will still use the same functionality that is used otherwise
     but we will pass the randomized pair of the page_id from trial randomization table to the task.html"""
-
-    randomized_page = trial_randomization.query.filter(and_(
-            trial_randomization.answer_set_idanswer_set==session['answer_set'], 
-            trial_randomization.page_idpage==page_id
-        )).first()
-
-    return randomized_page
+    return trial_randomization.get_randomized_page(page_id)
 
 
 def add_slider_answer(key, value, page_id=None):
@@ -269,7 +263,6 @@ def task(page_num):
 
 @task_blueprint.route('/completed')
 def completed():
-    
     session.pop('user', None)
     session.pop('exp_id', None)    
     session.pop('agree', None)
diff --git a/app/utils.py b/app/utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..a06c23ceda44af401265805c3b9ac94b0600ba34
--- /dev/null
+++ b/app/utils.py
@@ -0,0 +1,306 @@
+import os
+import tempfile
+import time
+import json
+from itertools import zip_longest
+import concurrent.futures
+from flask import send_file
+from flask_socketio import emit
+from app import app
+from app.models import background_question, background_question_answer, \
+    page, question, answer_set, answer, embody_answer, embody_question
+
+
+def timeit(method):
+    def timed(*args, **kw):
+        ts = time.time()
+        result = method(*args, **kw)
+        te = time.time()
+        if 'log_time' in kw:
+            name = kw.get('log_name', method.__name__.upper())
+            kw['log_time'][name] = int((te - ts) * 1000)
+        else:
+            app.logger.info('{} {:2.2f} ms'.format(method.__name__, (te - ts) * 1000))
+        return result
+
+    return timed
+
+
+def map_values_to_int(values: dict):
+    #values = [map(int, i) for i in list(values.values())]
+    return zip_longest(*values.values(), fillvalue=None)
+
+
+def calculate_mean(values: list) -> float:
+    n_answers = sum(x is not None for x in values)
+    sum_of_answers = float(sum(filter(None, values)))
+    mean = sum_of_answers / n_answers
+    return round(mean, 2)
+
+
+def get_mean_from_slider_answers(answers):
+    return [calculate_mean(values) for values in map_values_to_int(answers)]
+
+
+def saved_data_as_file(filename, data):
+    """write CSV data to temporary file on host and send that file
+    to requestor"""
+    try:
+        fd, path = tempfile.mkstemp()
+        with os.fdopen(fd, 'w') as tmp:
+            tmp.write(data)
+            tmp.flush()
+            return send_file(path,
+                             mimetype='text/csv',
+                             as_attachment=True,
+                             attachment_filename=filename)
+    finally:
+        os.remove(path)
+
+
+def get_values_from_list_of_answers(page_question, answers):
+    page_id = page_question[0]
+    question_id = page_question[1]
+    for _answer in answers:
+        try:
+            if _answer.question_idquestion == question_id and \
+                    _answer.page_idpage == page_id:
+                return int(_answer.answer)
+        except AttributeError:
+            if _answer.embody_question_idembody == question_id and \
+                    _answer.page_idpage == page_id:
+                return _answer
+    return None
+
+
+def question_matches_answer(question, answer):
+    if (answer.page_idpage == question[0] and answer.question() == question[1]):
+        return True
+    return False
+
+
+def map_answers_to_questions(answers, questions):
+    '''
+    questions = [(4, 1), (4, 2), (5, 1), (5, 2), (6, 1), (6, 2)]
+    +
+    answers = [{p:6, q:1, a:100}, {p:6, q:2, a:99}]
+    ->
+    partial_answer = [None, None, None, None, 100, 99]
+    '''
+
+    # results = []
+    results = list(map(lambda x: None, questions))
+
+    nth_answer = 0
+
+    for nth_question, question in enumerate(questions):
+
+        try:
+            current_answer = answers[nth_answer]
+        except IndexError:
+            break
+
+        if question_matches_answer(question, current_answer):
+            results[nth_question] = current_answer.result()
+            nth_answer += 1
+
+    return results
+
+    '''
+    return list(map(
+        lambda x: get_values_from_list_of_answers(x, answers),
+        questions))
+    '''
+
+
+'''
+select sub.answer_set_idanswer_set, group_concat(concat(
+    COALESCE(sub.aa, ''), 
+    COALESCE(sub.ab, ''), 
+    COALESCE(sub.ba, ''), 
+    COALESCE(sub.bb, ''), 
+    COALESCE(sub.ca, ''), 
+    COALESCE(sub.cb, '')
+)) 
+FROM (     
+    select  *, 
+        case when page_idpage = 4 and question_idquestion = 1 then answer end as aa,
+        case when page_idpage = 4 and question_idquestion = 2 then answer end as ab,
+        case when page_idpage = 5 and question_idquestion = 1 then answer end as ba,
+        case when page_idpage = 5 and question_idquestion = 2 then answer end as bb,
+        case when page_idpage = 6 and question_idquestion = 1 then answer end as ca,
+        case when page_idpage = 6 and question_idquestion = 2 then answer end as cb
+    from answer where answer_set_idanswer_set in ( select idanswer_set from answer_set where experiment_idexperiment = 2 and answer_counter != 0 )  
+) as sub 
+group by sub.answer_set_idanswer_set;
+
+
+
+
+# all possible page/question comobs
+select distinct p.idpage, q.idquestion from question q join page p on p.experiment_idexperiment=q.experiment_idexperiment where p.experiment_idexperiment = 2 order by p.idpage,q.idquestion;
+'''
+
+
+
+@timeit
+def generate_csv(exp_id):
+
+    # answer sets with participant ids
+    participants = answer_set.query.filter_by(
+        experiment_idexperiment=exp_id).all()
+
+    # pages aka stimulants
+    pages = page.query.filter_by(experiment_idexperiment=exp_id).all()
+
+    # background questions
+    bg_questions = background_question.query.filter_by(
+        experiment_idexperiment=exp_id).all()
+
+    # question
+    questions = question.query.filter_by(experiment_idexperiment=exp_id).all()
+
+    # embody questions
+    embody_questions = embody_question.query.filter_by(
+        experiment_idexperiment=exp_id).all()
+
+    csv = ''
+
+    # create CSV-header
+    header = 'participant id;'
+    header += ';'.join([str(count) + '. bg_question: ' + q.background_question.strip()
+                        for count, q in enumerate(bg_questions, 1)])
+
+    for idx in range(1, len(pages) + 1):
+        if len(questions) > 0:
+            header += ';' + ';'.join(['page' + str(idx) + '_' + str(count) + '. slider_question: ' +
+                                      question.question.strip() for count, question in enumerate(questions, 1)])
+
+    for idx in range(1, len(pages) + 1):
+        if len(embody_questions) > 0:
+            header += ';' + ';'.join(['page' + str(idx) + '_' + str(count) + '. embody_question: ' +
+                                      question.picture.strip() for count, question in enumerate(embody_questions, 1)])
+
+    csv += header + '\r\n'
+
+    # filter empty answer_sets
+    participants = list(filter(lambda participant: True if int(
+        participant.answer_counter) > 0 else False, participants))
+
+    len_participants = len(participants)
+
+    # We can use a with statement to ensure threads are cleaned up promptly
+    with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
+        # Start the load operations and mark each future with its URL
+        future_to_answer = {
+            executor.submit(generate_answer_row, participant, pages, questions, embody_questions): participant
+            for participant in participants}
+
+        for nth, future in enumerate(concurrent.futures.as_completed(future_to_answer)):
+            # for testing purpose
+            # answer_row = future_to_answer[future]
+            try:
+                emit('progress', {'done': nth, 'from': len_participants})
+                data = future.result()
+                csv += data + '\r\n'
+            except Exception as exc:
+                print('generated an exception: {}'.format(exc))
+                return exc
+
+    return csv
+
+
+def generate_answer_row(participant, pages, questions, embody_questions):
+    # TODO: refactor
+
+    with app.app_context():
+
+        answer_row = ''
+
+        # append user session id
+        answer_row += participant.session + ';'
+
+        # append background question answers
+        bg_answers = background_question_answer.query.filter_by(
+            answer_set_idanswer_set=participant.idanswer_set).all()
+        bg_answers_list = [str(a.answer).strip() for a in bg_answers]
+        answer_row += ';'.join(bg_answers_list) + ';'
+
+        # append slider answers
+        slider_answers = answer.query.filter_by(
+            answer_set_idanswer_set=participant.idanswer_set) \
+            .order_by(answer.page_idpage, answer.question_idquestion) \
+            .all()
+
+        pages_and_questions = {}
+
+        for p in pages:
+            questions_list = [(p.idpage, a.idquestion) for a in questions]
+            pages_and_questions[p.idpage] = questions_list
+
+        _questions = [
+            item for sublist in pages_and_questions.values() for item in sublist]
+
+        answers_list = map_answers_to_questions(slider_answers, _questions)
+
+        # typecast elemnts to string
+        answers_list = [str(a).strip() for a in answers_list]
+
+        answer_row += ';'.join(answers_list) + \
+            ';' if slider_answers else len(
+                questions) * len(pages) * ';'
+
+        # append embody answers (coordinates)
+        # save embody answers as bitmap images
+        embody_answers = embody_answer.query.filter_by(
+            answer_set_idanswer_set=participant.idanswer_set) \
+            .order_by(embody_answer.page_idpage) \
+            .all()
+
+        pages_and_questions = {}
+
+        for p in pages:
+            questions_list = [(p.idpage, a.idembody) for a in embody_questions]
+            pages_and_questions[p.idpage] = questions_list
+
+        _questions = [
+            item for sublist in pages_and_questions.values() for item in sublist]
+
+        _embody_answers = map_answers_to_questions(embody_answers, _questions)
+
+        answers_list = []
+
+        for answer_data in _embody_answers:
+            if not answer_data:
+                answers_list.append('')
+                continue
+
+            try:
+                coordinates = json.loads(answer_data)
+                em_height = coordinates.get('height', 600) + 2
+                em_width = coordinates.get('width', 200) + 2
+
+                coordinates_to_bitmap = [
+                    [0 for x in range(em_height)] for y in range(em_width)]
+
+                coordinates = list(
+                    zip(coordinates.get('x'), coordinates.get('y')))
+
+                for point in coordinates:
+
+                    try:
+                        # for every brush stroke, increment the pixel 
+                        # value for every brush stroke
+                        coordinates_to_bitmap[int(point[0])][int(point[1])] += 0.1
+                    except IndexError:
+                        continue
+
+                answers_list.append(json.dumps(coordinates_to_bitmap))
+
+            except ValueError as err:
+                app.logger(err)
+
+        answer_row += ';'.join(answers_list) if embody_answers else \
+            len(embody_questions) * len(pages) * ';'
+
+        return answer_row
diff --git a/config.py b/config.py
index 26d0769b571c8e7f552ff5588aef47003fa91695..632122a826069dbc4e2203de761d258a5c66a275 100644
--- a/config.py
+++ b/config.py
@@ -1,11 +1,12 @@
+from decouple import config
 import os
 basedir = os.path.abspath(os.path.dirname(__file__))
 
-class Config(object):
 
-    #seret key is set in __ini__.py
-    #SECRET_KEY = os.environ.get('SECRET_KEY') or 'you-will-never-guess'    
+class Config(object):
 
+    # seret key is set in __ini__.py
+    #SECRET_KEY = os.environ.get('SECRET_KEY') or 'you-will-never-guess'
 
     LANGUAGES = ['en', 'fi', 'fa', 'el', 'it', 'zh']
 
@@ -17,23 +18,25 @@ class Config(object):
     SQLALCHEMY_TRACK_MODIFICATIONS = False
     """
 
-    #MariaDB mysql database settings
+    # MariaDB mysql database settings
+
+    MYSQL_USER = config('MYSQL_USER')
+    MYSQL_PASSWORD = config('MYSQL_PASSWORD')
+    MYSQL_SERVER = config('MYSQL_SERVER')
+    MYSQL_DB = config('MYSQL_DB')
+
+    SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://'+MYSQL_USER+':' + \
+        MYSQL_PASSWORD+'@'+MYSQL_SERVER+'/'+MYSQL_DB+'?charset=utf8mb4'
 
-    MYSQL_USER = 'rating'
-    MYSQL_PASSWORD = 'rating_passwd'
-    MYSQL_SERVER = 'localhost'
-    MYSQL_DB = 'rating_db' 
-    
-    SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://'+MYSQL_USER+':'+MYSQL_PASSWORD+'@'+MYSQL_SERVER+'/'+MYSQL_DB+'?charset=utf8mb4'
-   
     SQLALCHEMY_TRACK_MODIFICATIONS = False
 
     SQLALCHEMY_ENGINE_OPTIONS = {
-	"pool_pre_ping": True,
-	"pool_recycle": 300,
-	"max_overflow": 30,
-	"pool_size": 20
+        "pool_pre_ping": True,
+        "pool_recycle": 60,
+        "max_overflow": 30,
+        "pool_size": 20
     }
-    
+
     TEMPLATES_AUTO_RELOAD = True
+
     DEBUG = False
diff --git a/embody_plot.py b/embody_plot.py
index e72e0656b25cbd656f05ff2366c5aa54ea5eca62..f540d77fafee5a5fbdf446d50d2732bc08b4be4d 100644
--- a/embody_plot.py
+++ b/embody_plot.py
@@ -119,13 +119,13 @@ def timeit(method):
 
     return timed
 
-import sys
 
 @timeit
 def get_coordinates(idpage, idembody=None, select_clause=SELECT_BY_PAGE_AND_PICTURE):
     """Select all drawn points from certain stimulus and plot them onto 
     the human body"""
 
+    # init db
     db = MyDB()
     db.query(select_clause, (idpage,idembody))
 
@@ -138,12 +138,13 @@ def get_coordinates(idpage, idembody=None, select_clause=SELECT_BY_PAGE_AND_PICT
         image_path = db._db_cur.fetchone()[0]
         image_path = './app' + image_path
 
-
         # Draw image
         plt = plot_coordinates(coordinates, image_path)
     else:
         plt = plot_coordinates(coordinates, DEFAULT_IMAGE_PATH)
 
+    # close db connection
+    db.__del__()
 
     # Save image to ./app/static/ 
     img_filename = 'PAGE-' + str(idpage) + '-' + DATE_STRING + '.png'
@@ -187,6 +188,7 @@ def plot_coordinates(coordinates, image_path=DEFAULT_IMAGE_PATH):
 
     # Total amount of points
     points_count = len(coordinates['coordinates']) 
+    step = 1
 
     # Load image to a plot
     image = mpimg.imread(image_path)
@@ -209,21 +211,26 @@ def plot_coordinates(coordinates, image_path=DEFAULT_IMAGE_PATH):
         for idx, point in enumerate(coordinates["coordinates"]):
 
             try:
-            	frame[int(point[1]), int(point[0])] = 1
+                frame[int(point[1]), int(point[0])] = 1
             except IndexError as err:
-            	app.logger.info(err)
-
-            point = ndimage.gaussian_filter(frame, sigma=5)
-            ax2.imshow(point, cmap='hot', interpolation='none')
+                app.logger.info(err)
 
             # Try to send progress information to socket.io
-            try:
-                emit('progress', {'done':idx+1/points_count, 'from':points_count})
-                socketio.sleep(0)
-            except RuntimeError as err:
-                print(err)
+            if idx == 0:
                 continue
 
+            if round((idx / points_count) * 100) % (step * 5) == 0:
+                try:
+                    emit('progress', {'done': step * 5, 'from': 100})
+                    socketio.sleep(0.05)
+                except RuntimeError:
+                    continue
+
+                step += 1
+
+        point = ndimage.gaussian_filter(frame, sigma=5)
+        ax2.imshow(point, cmap='hot', interpolation='none')
+
         image_mask = mpimg.imread(IMAGE_PATH_MASK)
         ax2.imshow(image_mask)
 
@@ -238,7 +245,7 @@ def plot_coordinates(coordinates, image_path=DEFAULT_IMAGE_PATH):
     ax1.plot(coordinates["x"],coordinates["y"], 'ro', alpha=0.2)
     ax1.imshow(image, alpha=0.6)
 
-    app.logger.info("iamge plotted")
+    app.logger.info("image plotted")
 
     # return figure for saving/etc...
     return fig
diff --git a/plot_image.py b/plot_image.py
new file mode 100644
index 0000000000000000000000000000000000000000..bc723397bcdf5b66a8bd2b2adb96da1cdbd9c2a9
--- /dev/null
+++ b/plot_image.py
@@ -0,0 +1,120 @@
+'''
+Script for testing embody drawing results from onni.utu.fi
+
+Install requirements:
+pip install numpy
+pip install matplotlib
+
+Usage:
+Export data from onni.utu.fi and after that run the script in the same folder
+by passing exported_file as a parameter to the script
+
+$ python plot_image.py <exported_file>.csv
+
+Program prints header of the file, from which you must select column where the 
+image data is. After you have selected the right column, program prints the 
+drawing results from embody answers.
+
+If you want the program to draw default embody image to the background, then 
+you must put a copy of the 'dummy_6000.png' -file (this is the same that is used 
+in onni.utu.fu) to the same path as the script.
+
+'''
+
+import copy
+
+import numpy as np
+import matplotlib.pyplot as plt
+import csv
+import sys
+import json
+
+csv.field_size_limit(sys.maxsize)
+
+
+def show_images(images, cols=1, titles=None):
+    """Display a list of images in a single figure with matplotlib.
+    Parameters
+    ---------
+    images: List of np.arrays compatible with plt.imshow.
+
+    cols (Default = 1): Number of columns in figure (number of rows is 
+                        set to np.ceil(n_images/float(cols))).
+
+    titles: List of titles corresponding to each image. Must have
+            the same length as titles.
+    """
+
+    # default embody image for the background
+
+    try:
+        background = True
+        default_img = plt.imread("./dummy_600.png")
+    except FileNotFoundError:
+        background = False
+
+    # get a copy of the gray color map
+    my_cmap = copy.copy(plt.cm.get_cmap('gray'))
+    # set how the colormap handles 'bad' values
+    my_cmap.set_bad(alpha=0)  
+
+
+    assert((titles is None) or (len(images) == len(titles)))
+    n_images = len(images)
+    if titles is None:
+        titles = ['Image (%d)' % i for i in range(1, n_images + 1)]
+    fig = plt.figure()
+    for n, (image, title) in enumerate(zip(images, titles)):
+
+        a = fig.add_subplot(cols, np.ceil(n_images/float(cols)), n + 1)
+
+        # draw points from users answers
+        plt.imshow(image, cmap=my_cmap)
+
+        # draw default background image with transparency on top of the points
+        if background:
+            plt.imshow(default_img, extent=[0, 200, 600, 0], alpha=0.33)
+
+        a.set_title(title)
+
+    fig.set_size_inches(np.array(fig.get_size_inches()) * n_images)
+    plt.show()
+
+
+if __name__ == '__main__':
+
+    images = []
+    titles = []
+
+    # filename = 'experiment_1_2020-05-20.csv'
+    filename = sys.argv[1]
+
+    with open(filename, 'r+') as csvfile:
+        for row_no, row in enumerate(csv.reader(csvfile, delimiter=';')):
+
+            # parse header
+            if row_no == 0:
+                for column, title in enumerate(row):
+                    print("Column (no. {}): {}".format(column, title))
+
+                print('Enter the column number which has image data:')
+                x = int(input())
+                continue
+
+            try:
+                np_array = np.array(eval(row[x]))
+            except NameError:
+                print(
+                    "Column didn't contain image data. Try again with different column number.")
+            except SyntaxError:
+                continue
+            except IndexError:
+                continue
+
+            np_array = np.transpose(np_array)
+            images.append(np_array)
+
+            # add id of the answerer to the image
+            titles.append(row[0])
+
+    show_images(images, titles=titles)
diff --git a/requirements.txt b/requirements.txt
index 9e433b6c68e2ba465520156fdc3537bdd72ae72e..9c4419e6189f07406c5f3dfa1d9dee67b492a6b0 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -5,7 +5,8 @@ Flask-Cors==3.0.7
 Flask-Login==0.4.1
 Flask-Migrate==2.2.1
 Flask-Session==0.3.1
-Flask-SocketIO==3.3.2
+Flask-SocketIO==4.3.0
+# Flask-SocketIO==3.3.2
 Flask-SQLAlchemy==2.3.2
 Flask-Uploads==0.2.1
 Flask-WTF==0.14.2
@@ -17,11 +18,19 @@ mysql-connector==2.2.9
 networkx==2.2
 numpy==1.16.2
 PyMySQL==0.9.3
-python-engineio==3.5.1
-python-socketio==3.1.2
+pyparsing==2.3.1
+python-dateutil==2.7.3
+python-editor==1.0.3
+python-engineio==3.13.0
+# python-engineio==3.5.1
+# python-socketio==3.1.2
+python-socketio==4.6.0
+pytz==2018.7
+rope==0.12.0
 scipy==1.2.1
 SQLAlchemy==1.2.8
 uuid==1.30
 Werkzeug==0.14.1
 WTForms==2.2.1
-WTForms-SQLAlchemy==0.1
\ No newline at end of file
+WTForms-SQLAlchemy==0.1
+python-decouple