diff --git a/app/experiment/templates/experiment_statistics.html b/app/experiment/templates/experiment_statistics.html index 978d97c7777bb0502735542b4560339a6724fd38..63170c5184f46e3d321f4d3236277b4ed0d2ddc6 100644 --- a/app/experiment/templates/experiment_statistics.html +++ b/app/experiment/templates/experiment_statistics.html @@ -35,18 +35,16 @@ </tr> <tr> <td>Number of finished ratings:</td> - <td>{{ finished_ratings }}</td> - </tr> - <tr> - <td><a class="btn btn-primary btn-info" href="{{ url_for('download_csv', exp_id=exp.idexperiment) }}" role="button">Export results (csv)</a></td> - <td></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> + </td> </tr> </tbody> </table> {% endfor %} -<h1 class="container mt-5 display-4 text-left"><br>Rating task question headers:</h1> +<h1 class="container mt-5 display-6 text-left"><br>Slider question headers:</h1> <br> <table class="table"> <thead> @@ -69,7 +67,40 @@ </tbody> </table> -<h1 class="container mt-5 display-4 text-left"><br>Rating task stimulus headers:</h1> + +<h1 class="container mt-5 display-6 text-left"><br>Slider question answers: (Page ID/Question ID)</h1> +<br> + + +<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 %} + </tr> + </thead> + <tbody> + {% for participant in participants_and_answers %} + <tr> + <td>{{ participant }}</td> + {% for answer in participants_and_answers[participant] %} + <td>{{ answer }}</td> + {% endfor %} + </tr> + {% endfor %} + + </tbody> +</table> + + +<h1 class="container mt-5 display-6 text-left"><br>Embody pictures and answers:</h1> <br> <table class="table"> <thead> @@ -87,8 +118,16 @@ {% 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> {% else %} - <td>{{ s.media }}</td> + <td>{{ s.text }}</td> {% endif %} <td><img src="{{ embody_picture.picture }}" class="thumbnail" /></td> @@ -116,43 +155,14 @@ <img class="embody-image-container"> -<h1 class="container mt-5 display-4 text-left"><br>Rating task values: (Stimulus ID/Question ID)</h1> -<br> - - -<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 %} - </tr> - </thead> - <tbody> - {% for participant in participants_and_answers %} - <tr> - <td>{{ participant }}</td> - {% for answer in participants_and_answers[participant] %} - <td>{{ answer[3] }}</td> - {% endfor %} - </tr> - {% endfor %} - </tbody> -</table> - -<h1 class="container mt-5 display-4 text-left"><br>Background question answers:</h1> +<h1 class="container mt-5 display-6 text-left"><br>Background question answers:</h1> <br> <table class="table"> <thead> <tr> - <th scope="col" nowrap>Question:</th> + <th scope="col" nowrap>Participant</th> {% for bg in bg_questions %} <th scope="col" nowrap>{{ bg.background_question }}</th> @@ -168,6 +178,8 @@ {% endfor %} </tr> {% endfor %} + + </tbody> </table> diff --git a/app/experiment/views.py b/app/experiment/views.py index 40aaee1a78b7dc224b1a77f462f4e17c2864b4ea..c3d925315270f262d982e02f9e8c58e69d08c673 100644 --- a/app/experiment/views.py +++ b/app/experiment/views.py @@ -893,6 +893,14 @@ def statistics(): 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( + 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() + #Rating task headers question_headers = question.query.filter_by(experiment_idexperiment=exp_id).all() @@ -903,47 +911,56 @@ def statistics(): 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 - - participants_and_answers = {} - + + slider_answers = {} for participant in participants: - - answers = answer.query.filter_by(answer_set_idanswer_set=participant.idanswer_set).all() - answers_list = [(a.idanswer, a.question_idquestion, a.answer_set_idanswer_set, a.answer, a.page_idpage) for a in answers] - participants_and_answers[participant.session] = answers_list + + # 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] + + # map slider_answers from str to int and calculate mean + a = [map(int,i) for i in list(slider_answers.values())] + slider_answers['mean'] = [float(sum(l))/len(l) for l in zip(*a)] + #Background question answers - bg_questions = background_question.query.filter_by( experiment_idexperiment=exp_id).all() bg_answers_for_participants = {} for participant in participants: - 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 + # list only finished answer sets + if experiment_page_count == participant.answer_counter: + 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 - #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() # 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=participants_and_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 + ) import embody_plot diff --git a/app/forms.py b/app/forms.py index 7d9632c1219279ed4482508d5655925778b10879..d532931f9aa3db95a8e1f0cf80b65b6d6022be46 100644 --- a/app/forms.py +++ b/app/forms.py @@ -81,9 +81,9 @@ class TestForm2(Form): questions1 = SelectField() + #Forms for editing functions - class CreateExperimentForm(Form): name = StringField('Name', [validators.DataRequired()]) @@ -147,6 +147,7 @@ class CreateEmbodyForm(Form): picture = FileField('Upload picture') submit = SubmitField('Send') + class EditQuestionForm(Form): left = StringField('left_scale', [validators.DataRequired()]) @@ -168,7 +169,6 @@ class UploadResearchBulletinForm(Form): file = FileField('Upload file') submit = SubmitField('Send') - class EditPageForm(Form): @@ -183,6 +183,7 @@ class RemoveExperimentForm(Form): remove = TextAreaField('Remove') submit = SubmitField('Send') + class GenerateIdForm(Form): number = IntegerField('number', [validators.DataRequired()]) diff --git a/app/routes.py b/app/routes.py index c740da9fb597d7df9129c7dd12788268f195e4cb..da3705edce18427fe45f003f14b93f24f9725807 100644 --- a/app/routes.py +++ b/app/routes.py @@ -382,47 +382,50 @@ def download_csv(): header += ';' + ';'.join(['page' + str(idx) + '_' + str(count) +'. embody_question: '+ question.picture for count,question in enumerate(embody_questions, 1)]) csv += header + '\r\n' - answer_row = '' for participant in participants: - try: + # list only finished answer sets + if experiment_page_count == participant.answer_counter: - # append user session id - answer_row += participant.session + ';' + try: - # append background question answers - 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] - 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 = [ a.answer for a in slider_answers] - answer_row += ';'.join(answers_list) + ';' if slider_answers else '' - - # 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: - 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'])): - coordinates_to_bitmap[point[0]][point[1]] += 0.1 - - answers_list.append(json.dumps(coordinates_to_bitmap)) - - # 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 '' - - except TypeError as err: - print(err) - - csv += answer_row + '\r\n' - 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).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: + 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'])): + coordinates_to_bitmap[point[0]][point[1]] += 0.1 + + answers_list.append('hello') + #answers_list.append(json.dumps(coordinates_to_bitmap)) + + # 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() diff --git a/app/static/js/canvas.js b/app/static/js/canvas.js index d98fb76f6010fcbe15eb69f4b1c0d11e702a0749..5cc87d247cd26737b4f0bf0a5c15bc20f133548d 100644 --- a/app/static/js/canvas.js +++ b/app/static/js/canvas.js @@ -6,16 +6,7 @@ const defaultEmbodySource = '/static/img/dummy_600.png'; $(document).ready(function()Â { // Init draw variables - /* - OO-style coordinates: - var paint; - var point = { - x: null, - y: null, - r: 13 - } - var points = new Array() - */ + var clickX = new Array(); var clickY = new Array(); var clickRadius = new Array(); @@ -36,9 +27,9 @@ $(document).ready(function()Â { //var oldimg = document.getElementById("baseImage"); var img = $('.embody-image.selected-embody')[0] - $(img).on('load', function() { + img.onload = function()Â { drawImage() - }) + } } catch (e) { console.log(e) @@ -63,6 +54,8 @@ $(document).ready(function()Â { } } + drawImage() + // Click handlers canvas.mousedown(function(e){ var mouseX = e.pageX - this.offsetLeft; @@ -85,6 +78,27 @@ $(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; + if (paint && pointInsideBaseImage([mouseX, mouseY])){ + addClick(mouseX, mouseY, true); + redraw(); + } + }); + + canvas.bind('touchstart', function(e){ + e.preventDefault() + var mouseX = e.touches[0].pageX - this.offsetLeft; + var mouseY = e.touches[0].pageY - this.offsetTop; + paint = true; + if (pointInsideBaseImage([mouseX, mouseY])) { + addClick(mouseX, mouseY); + redraw(); + } + }); + canvas.mouseup(function(e){ paint = false; }); @@ -96,7 +110,6 @@ $(document).ready(function()Â { $("#embody-canvas").bind('DOMMouseScroll', changeBrushSize) // DOMMouseScroll is only for firefox - //$(".canvas-container").bind('wheel', changeBrushSize) function changeBrushSize(event) { event.preventDefault() @@ -149,7 +162,6 @@ $(document).ready(function()Â { if ($(img).hasClass('last-embody')) { // Send data to db try { - console.log(points) points = JSON.stringify(points) $("#canvas-data").val(points); $("#canvas-form").submit(); @@ -259,9 +271,5 @@ $(document).ready(function()Â { clickY = [] clickDrag = [] } - - - - drawImage() }); \ No newline at end of file diff --git a/app/task/templates/task.html b/app/task/templates/task.html index d0619ba75cf5855f2ae20d6e4fc7a0edd3a441fe..622f199961baef2ca8425ef0ae98b4797fccaae0 100644 --- a/app/task/templates/task.html +++ b/app/task/templates/task.html @@ -94,10 +94,6 @@ {% if form.__name__ == 'embody' %} - - - - <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> diff --git a/create_rating_db.sql b/create_rating_db.sql index c29b62e5ed8111dc85a397c9826a9c686d407e96..9878fe678c7758c323727422a4d62ee28953705a 100644 --- a/create_rating_db.sql +++ b/create_rating_db.sql @@ -187,7 +187,7 @@ CREATE TABLE embody_question ( ALTER TABLE embody_answer ADD COLUMN (embody_question_idembody INTEGER DEFAULT 0); ALTER TABLE embody_answer ADD CONSTRAINT FOREIGN KEY (embody_question_idembody) REFERENCES embody_question (idembody); -/* Set flag if embody tool is enabled -> this is not the most modulart solution, but works for now */ +/* Set flag if embody tool is enabled -> this is not the most modular solution, but works for now */ ALTER TABLE experiment ADD COLUMN (embody_enabled BOOLEAN DEFAULT 0); /* Set current answer type (embody/slider/etc..) so returning users are routed to correct question */ diff --git a/embody_plot.py b/embody_plot.py index 1e27ae05fcca827d4d17faed48eb03c017e2f8b5..38a3ade3e44df7abb1738f4032ac9d6462f590c3 100644 --- a/embody_plot.py +++ b/embody_plot.py @@ -38,6 +38,7 @@ from matplotlib.figure import Figure from flask_socketio import emit from app import socketio + # Hard coded image size for default embody image WIDTH = 207 HEIGHT = 600 @@ -191,15 +192,16 @@ def plot_coordinates(coordinates, image_path=DEFAULT_IMAGE_PATH): if image_path == DEFAULT_IMAGE_PATH: for idx, point in enumerate(coordinates["coordinates"]): - frame[point[1], point[0]] = 1 + frame[int(point[1]), int(point[0])] = 1 point = ndimage.gaussian_filter(frame, sigma=5) ax2.imshow(point, cmap='hot', interpolation='none') # Try to send progress information to socket.io try: - socketio.sleep(0) emit('progress', {'done':idx+1/points_count, 'from':points_count}) + socketio.sleep(0) except RuntimeError as err: + print(err) continue image_mask = mpimg.imread(IMAGE_PATH_MASK) diff --git a/requirements.txt b/requirements.txt index 1d3586c117b51bf3b70f3b18287f5ca8e0b1b39d..aa01c65b14a53c72103b395363501e5bb51eaaa9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,27 +1,57 @@ alembic==0.9.9 +autopep8==1.4.4 +Babel==2.6.0 click==6.7 +cycler==0.10.0 +decorator==4.4.0 +dnspython==1.16.0 dominate==2.3.1 Flask==1.0.2 -Flask-Babel +Flask-Babel==0.12.2 Flask-Bootstrap==3.3.7.1 +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-SQLAlchemy==2.3.2 Flask-Uploads==0.2.1 Flask-WTF==0.14.2 +gevent==1.4.0 +greenlet==0.4.15 +gunicorn==19.9.0 itsdangerous==0.24 Jinja2==2.10 +kiwisolver==1.0.1 +lml==0.0.4 Mako==1.0.7 MarkupSafe==1.0 +matplotlib==3.0.3 +monotonic==1.5 +mysql-connector==2.2.9 +networkx==2.2 +numpy==1.16.2 +Pillow==6.0.0 +pkg-resources==0.0.0 +ply==3.11 +pycodestyle==2.5.0 +pyexcel==0.5.9.1 +pyexcel-io==0.5.9.1 +PyMySQL==0.9.3 +pyparsing==2.3.1 python-dateutil==2.7.3 python-editor==1.0.3 +python-engineio==3.5.1 +python-socketio==3.1.2 +pytz==2018.7 +rope==0.12.0 +scipy==1.2.1 six==1.11.0 +smop==0.41 SQLAlchemy==1.2.8 +texttable==1.5.0 uuid==1.30 visitor==0.1.3 Werkzeug==0.14.1 WTForms==2.2.1 WTForms-SQLAlchemy==0.1 -pyexcel -gunicorn==19.9.0