diff --git a/app/experiment/templates/experiment_statistics.html b/app/experiment/templates/experiment_statistics.html index d5c7b648cd770163e2c9404bfed649fab1508d60..d381258467ab0833b7bf0736a849152fed023155 100644 --- a/app/experiment/templates/experiment_statistics.html +++ b/app/experiment/templates/experiment_statistics.html @@ -78,43 +78,36 @@ <tr> <th scope="col" nowrap>Stimulus ID:</td> <th scope="col" nowrap>Stimulus:</th> + <th scope="col" nowrap>Embody:</th> </tr> </thead> <tbody> {% for s in stimulus_headers %} + <tr> - <td>{{ s.idpage }}</td> - {% if s.type == 'text' %} - <td>{{ s.text }}</td> - {% else %} - <td>{{ s.media }}</td> - {% endif %} + <td>{{ s.idpage }}</td> + {% if s.type == 'text' %} + <td>{{ s.text }}</td> + {% else %} + <td>{{ s.media }}</td> + {% endif %} + + + {% if experiment_info[0].embody_enabled %} + <td> + <button data-value={{ s.idpage }} class="btn btn-primary embody-get-drawing"> + <span class="spinner-border spinner-border-sm hidden"></span> + Draw + </button> + </td> + {% endif %} </tr> {% endfor %} </tbody> </table> - - - - - - - - - - - - - - - - - - - - +<img class="embody-image-container"> <h1 class="container mt-5 display-4 text-left"><br>Rating task values: (Stimulus ID/Question ID)</h1> @@ -147,11 +140,9 @@ </tbody> </table> - <h1 class="container mt-5 display-4 text-left"><br>Background question answers:</h1> <br> - <table class="table"> <thead> <tr> @@ -174,9 +165,6 @@ </tbody> </table> - - - - +<script src="{{ url_for('static', filename='js/getDrawing.js') }}" ></script> {% endblock %} \ No newline at end of file diff --git a/app/experiment/views.py b/app/experiment/views.py index 5c25d448e5b876c8c8da1804bb8316b380ca3e32..cb4b3188da9f0e1b43a90ac2048f0d1f9a1d86d3 100644 --- a/app/experiment/views.py +++ b/app/experiment/views.py @@ -2,6 +2,7 @@ import os import secrets +import json from flask import ( Flask, @@ -11,7 +12,8 @@ from flask import ( flash, redirect, url_for, - Blueprint + Blueprint, + jsonify ) from wtforms import Form @@ -26,6 +28,7 @@ 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 embody_answer from app.forms import ( CreateBackgroundQuestionForm, CreateQuestionForm, UploadStimuliForm, EditBackgroundQuestionForm, @@ -683,9 +686,21 @@ def edit_stimuli(): exp_id = request.args.get('exp_id', None) page_id = request.args.get('idpage', None) edit_page = page.query.filter_by(idpage=page_id).first() - form = EditPageForm(request.form, obj=edit_page) + form = EditPageForm(request.form) + #form = EditPageForm(request.form, obj=edit_page) + + # TODO: replacing image not working!! + + print(request.files.getlist("file")) + print("errors:", form.errors) + print("form:", form.__dict__) + print("type:", form.type.data) + print("media:", form.media.data) + print("file:", form.file.data) + print("text:", form.text.data) 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 edit_page.type != 'text': #remove old file @@ -834,3 +849,16 @@ def statistics(): 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) +import embody_plot +from flask_cors import CORS,cross_origin + +@experiment_blueprint.route('/create_embody', methods=['POST']) +@cross_origin() +def create_embody(): + + #page = request.args.get("page") + page = request.form["page"] + img_path = embody_plot.get_coordinates(page) + + #return send_file('static/' + img_path, 'test') + return json.dumps({'path':img_path}) diff --git a/app/static/css/main.css b/app/static/css/main.css index 16a94b8e1e482d81fb9c5413fc87591f5ac90d2b..7da83e0cc1f793db2f1bd93afac05330b11d3ad7 100644 --- a/app/static/css/main.css +++ b/app/static/css/main.css @@ -9,9 +9,6 @@ body { } -.hidden { - display: none; -} #embody-canvas { display: block; @@ -24,4 +21,31 @@ body { margin:10px; } -.crosshair {cursor: crosshair;} \ No newline at end of file +.crosshair {cursor: crosshair;} + + +@keyframes spinner-border { + to { + transform: rotate(360deg); + } +} +.spinner-border{ + display: inline-block; + width: 2rem; + height: 2rem; + vertical-align: text-bottom; + border: .25em solid currentColor; + border-right-color: transparent; + border-radius: 50%; + -webkit-animation: spinner-border .75s linear infinite; + animation: spinner-border .75s linear infinite;width: 1rem; +} +.spinner-border-sm{ + height: 1rem; + border-width: .2em; +} + +.hidden { + display: none; +} + diff --git a/app/static/js/canvas.js b/app/static/js/canvas.js index 8d27eeab40d016e95e54c0d2bfebcfd53f0f2e2a..e7f08018698b08ffe42edfa68ec1e7769d251dbb 100644 --- a/app/static/js/canvas.js +++ b/app/static/js/canvas.js @@ -22,8 +22,19 @@ $(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(); var clickDrag = new Array(); var paint; var drawRadius=13; @@ -105,6 +116,7 @@ $(document).ready(function()Â { function addClick(x, y, dragging=false) { clickX.push(x); clickY.push(y); + clickRadius.push(drawRadius); clickDrag.push(dragging); } @@ -185,9 +197,11 @@ $(document).ready(function()Â { } function saveData()Â { + var points = { x: clickX, - y: clickY + y: clickY, + r: clickRadius } points = JSON.stringify(points) diff --git a/app/static/js/getDrawing.js b/app/static/js/getDrawing.js new file mode 100644 index 0000000000000000000000000000000000000000..8d0ec0d56aa1bf8a6a36daf0fede85f0f087a867 --- /dev/null +++ b/app/static/js/getDrawing.js @@ -0,0 +1,35 @@ + + +const baseURI = 'http://127.0.0.1:8000/'; +var getDrawingURI = baseURI + 'experiment/create_embody'; + + +$(document).ready(function()Â { + + var drawButtons = $(".embody-get-drawing"); + var imageContainer = $(".embody-image-container") + var source = '' + + drawButtons.click(function(event) { + event.preventDefault() + + var spinner = $(event.target.firstElementChild) + spinner.removeClass("hidden") + + var pageId = this.dataset.value + + $.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") + }) + + }) + +}) \ No newline at end of file diff --git a/app/task/views.py b/app/task/views.py index 25e7904b1154946d9e2dc67ad84077a79e7450bd..a5f084dad5f4eb5d81c0006448f9f2dd9431f1ff 100644 --- a/app/task/views.py +++ b/app/task/views.py @@ -236,7 +236,9 @@ def task(page_num): if session['randomization'] == 'On': randomized_page_id = get_randomized_page(page_id).randomized_idpage randomized_stimulus = page.query.filter_by(idpage=randomized_page_id).first() + + print(session) return render_template( 'task.html', pages=pages, diff --git a/app/templates/base.html b/app/templates/base.html index 0299ecefe5e552d9707ff84d9dfe273eed7d162e..8b35de2f6aa5f6fefb6b715679f198893043e4e0 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -10,7 +10,7 @@ <meta name="author" content=""> <link rel="stylesheet" href="{{ url_for('static', filename='lib/css/bootstrap.min.css') }}" ></link> <link rel="stylesheet" href="{{ url_for('static', filename='css/main.css') }}" ></link> - <script src="{{ url_for('static', filename='lib/js/jquery-3.3.1.slim.min.js') }}" ></script> + <script src="{{ url_for('static', filename='lib/js/jquery-3.3.1.js') }}" ></script> <script src="{{ url_for('static', filename='lib/js/popper.min.js') }}" ></script> <script src="{{ url_for('static', filename='lib/js/bootstrap.min.js') }}" ></script> <title>Onni</title> diff --git a/create_rating_db.sql b/create_rating_db.sql index b454ca37d8b9b3d43bb38b1b26b89cd6068d0951..40516a890ad16c1350eee58667042f9e00f4f91f 100644 --- a/create_rating_db.sql +++ b/create_rating_db.sql @@ -146,7 +146,8 @@ CREATE TABLE answer ( FOREIGN KEY(question_idquestion) REFERENCES question (idquestion) ); -/* Embody answer (coordinates). Answer is saved as a json object {x:[1,2,100,..], y:[3,4,101,..]} */ +/* Embody answer (coordinates). Answer is saved as a json object: + {x:[1,2,100,..], y:[3,4,101,..], r:[13,13,8,...]} */ CREATE TABLE embody_answer ( idanswer INTEGER NOT NULL AUTO_INCREMENT, answer_set_idanswer_set INTEGER, diff --git a/embody_plot.py b/embody_plot.py index 65763ce01576f0c7f57b8ea7ffb9cfa0cd496cca..b7927b7297f48fb4f72c1cd4a1f0f1a8a6dc34fe 100644 --- a/embody_plot.py +++ b/embody_plot.py @@ -20,24 +20,67 @@ Run: python embody_plot.py """ -import matplotlib.pyplot as plt -import matplotlib.image as mpimg -import numpy as np -import scipy.ndimage as ndimage import sys import time +import datetime import json import resource import mysql.connector as mariadb +import io +import urllib, base64 +import argparse + +import numpy as np +import scipy.ndimage as ndimage +import matplotlib.pyplot as plt +import matplotlib.image as mpimg +from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas +from matplotlib.figure import Figure + +# Hard coded image size +WIDTH = 207 +HEIGHT = 600 + +# image paths +IMAGE_PATH = './app/static/img/dummy_600.png' +IMAGE_PATH_MASK = './app/static/img/dummy_600_mask.png' +STATIC_PATH = './app/static/' + +# Interpolation methods +METHODS = ['none','bilinear', 'bicubic', 'gaussian'] +# SELECT methods +SELECT_ALL = ("SELECT coordinates from embody_answer") +SELECT_BY_EXP_ID = 'select coordinates from embody_answer as em JOIN (SELECT idanswer_set FROM answer_set as a JOIN experiment as e ON a.experiment_idexperiment=e.idexperiment AND e.idexperiment=%s) as ida ON em.answer_set_idanswer_set=ida.idanswer_set' +SELECT_BY_ANSWER_SET = 'select coordinates from embody_answer WHERE answer_set_idanswer_set=%s' +SELECT_BY_PAGE = 'select coordinates from embody_answer WHERE page_idpage=%s' + +''' mariadb_connection = mariadb.connect( user='rating', password='rating_passwd', database='rating_db' - ) -cursor = mariadb_connection.cursor() +) +''' + +# Get date +now = datetime.datetime.now() +DATE_STRING = now.strftime("%Y-%m-%d") + + +class MyDB(object): + + def __init__(self): + self._db_connection = mariadb.connect(user='rating', password='rating_passwd', database='rating_db') + self._db_cur = self._db_connection.cursor() + + def query(self, query, params): + return self._db_cur.execute(query, params) + + def __del__(self): + self._db_connection.close() + -start = time.time() def matlab_style_gauss2D(shape=(1,1),sigma=5): """2D gaussian mask - should give the same result as MATLAB's @@ -52,80 +95,156 @@ def matlab_style_gauss2D(shape=(1,1),sigma=5): h /= sumh return h -# Hard coded image size -WIDTH = 207 -HEIGHT = 600 -# import image -image_path = './app/static/img/dummy_600.png' -image_path_mask = './app/static/img/dummy_600_mask.png' +def map_coordinates(a,b,c=None): + return [a,b,c] -# Load image to a plot -image = mpimg.imread(image_path) -image_mask = mpimg.imread(image_path_mask) -# Interpolation methods -methods = ['none','bilinear', 'bicubic', 'gaussian'] +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: + print('%r %2.2f ms' % \ + (method.__name__, (te - ts) * 1000)) + return result + + return timed + + +@timeit +def get_coordinates(selected_value, select_clause=SELECT_BY_PAGE): + """Select all drawn points from certain stimulus and plot them onto + the human body""" + + db = MyDB() + db.query(select_clause, (selected_value,)) + + # Get coordinates + coordinates = format_coordinates(db._db_cur) + + # Draw image + plt = plot_coordinates(coordinates) + + # Save image to ./app/static/ + img_filename = 'PAGE-' + str(selected_value) + '-' + DATE_STRING + '.png' + plt.savefig(STATIC_PATH + img_filename) + + # Return image path to function caller + return img_filename + + +def format_coordinates(cursor): + # Init coordinate arrays and radius of point + x=[] + y=[] + r=[] + standard_radius=13 + + # Loop through all of the saved coordinates and push them to coordinates arrays + for coordinate in cursor: + try: + coordinates = json.loads(coordinate[0]) + x.extend(coordinates['x']) + y.extend(coordinates['y']) + r.extend(coordinates['r']) + except KeyError: + standard_radiuses = np.full((1, len(coordinates['x'])), standard_radius).tolist()[0] + r.extend(standard_radiuses) + continue + + return { + "x":x, + "y":y, + "coordinates":list(map(map_coordinates, x,y,r)) + } + +def plot_coordinates(coordinates): + + # Load image to a plot + image = mpimg.imread(IMAGE_PATH) + image_mask = mpimg.imread(IMAGE_PATH_MASK) + + # Init plots + fig, (ax1, ax2) = plt.subplots(nrows=1, ncols=2) + + # Plot coordinates as points + ax1.set_title("raw points") + ax1.plot(coordinates["x"],coordinates["y"], 'ro', alpha=0.2) + ax1.imshow(image) + + # Draw circles from coordinates (imshow don't need interpolation) + # TODO: set sigma according to brush size! + ax2.set_title("gaussian disk around points") + frame = np.zeros((HEIGHT,WIDTH)) + + for point in coordinates["coordinates"]: + frame[point[1], point[0]] = 1 + point = ndimage.gaussian_filter(frame, sigma=5) + ax2.imshow(point, cmap='hot', interpolation='none') + + # TODO: send progress information to frontend + + ax2.imshow(image_mask) + + # Draw a gaussian heatmap on the whole image + # NOT USABLE + ''' + x_min = min(x) + x_max = max(x) + y_min = min(y) + y_max = max(y) + extent=[x_min, x_max, y_min, y_max] + extent_all = [0,WIDTH,0,HEIGHT] + plt.subplot2grid((2, 2), (1, 1)) + plt.title('gaussian heatmap') + plt.imshow(image) + plt.imshow(coordinates, extent=extent_all, cmap='hot', interpolation='gaussian') + plt.imshow(image_mask) + ''' + + # return figure for saving/etc... + return fig + + ''' + # Return image as bytes + fig = plt.gcf() + imgdata = io.BytesIO() + fig.savefig(imgdata, format='png') + imgdata.seek(0) # rewind the data + return imgdata.read() + + #Show image + mng = plt.get_current_fig_manager() + mng.resize(*mng.window.maxsize()) + plt.show() + ''' + + +if __name__=='__main__': + + arg_parser = argparse.ArgumentParser(description='Draw bodily maps of emotions') + arg_parser.add_argument('-s','--stimulus', help='Select drawn points from certain stimulus', required=False, action='store_true') + arg_parser.add_argument('-e','--experiment', help='Select drawn points from certain experiment', required=False, action='store_true') + arg_parser.add_argument('-a','--answer-set', help='Select drawn points from certain answer_set', required=False, action='store_true') + arg_parser.add_argument('integers', metavar='N', type=int, nargs='+', help='an integer for the accumulator') + args = vars(arg_parser.parse_args()) + value = args['integers'][0] + + if args['stimulus']: + get_coordinates(value, SELECT_BY_PAGE) + elif args['experiment']: + get_coordinates(value, SELECT_BY_EXP_ID) + elif args['answer_set']: + get_coordinates(value, SELECT_BY_ANSWER_SET) + else: + print("No arguments given. Exit.") + sys.exit(0) -SELECT_ALL = ("SELECT coordinates from embody_answer") -SELECT_BY_EXP_ID = 'select coordinates from embody_answer as em JOIN (SELECT idanswer_set FROM answer_set as a JOIN experiment as e ON a.experiment_idexperiment=e.idexperiment AND e.idexperiment=%s) as ida ON em.answer_set_idanswer_set=ida.idanswer_set' -exp_id = 2 -cursor.execute(SELECT_BY_EXP_ID, (exp_id,)) - -# Init coordinate arrays -x=[] -y=[] - -# Loop through all of the saved coordinates and push them to coordinates arrays -for coordinate in cursor: - coordinates = json.loads(coordinate[0]) - x.extend(coordinates['x']) - y.extend(coordinates['y']) - - -def map_coordinates(a,b): - return [a,b] - -coordinates = list(map(map_coordinates, x,y)) - -# Plot coordinates as points -plt.subplot2grid((2, 2), (0, 0)) -plt.title("raw points") -plt.plot(x,y, 'ro', alpha=0.2) -plt.imshow(image) -plt.grid(True) - -# Draw circles from coordinates (imshow don't need interpolation) -plt.subplot2grid((2, 2), (0, 1)) -plt.title("gaussian disk around points") -frame = np.zeros((HEIGHT,WIDTH)) -for point in coordinates: - frame[point[1], point[0]] = 1 - point = ndimage.gaussian_filter(frame, sigma=5) - plt.imshow(point, cmap='hot', interpolation='none') - -plt.imshow(image_mask) -plt.grid(True) - -# Draw a gaussian heatmap on the whole image -# NOT USABLE -x_min = min(x) -x_max = max(x) -y_min = min(y) -y_max = max(y) -extent=[x_min, x_max, y_min, y_max] -extent_all = [0,WIDTH,0,HEIGHT] -plt.subplot2grid((2, 2), (1, 1)) -plt.title('gaussian heatmap') -plt.imshow(image) -plt.imshow(coordinates, extent=extent, cmap='hot', interpolation='gaussian') -plt.imshow(image_mask) - -end = time.time() -print("Drawing image took:", end - start) - -mng = plt.get_current_fig_manager() -mng.resize(*mng.window.maxsize()) -plt.show()