diff --git a/.gitignore b/.gitignore index 876e85fbed66158652992281c4869c87a9daf489..06c7914e0a2b2a01fa929f3cbadf0d46dcb66f41 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ __pycache__/ /venv +/rating /logs /app/static/experiment_stimuli cmd.txt @@ -10,4 +11,5 @@ config.py *.pyc *.db /embody +/app/static/lib diff --git a/app/__init__.py b/app/__init__.py index eb3cdb8b7300c4b4b021d1d213e9182ea36f5d49..330168e9633e6edc03cfe757a708df994a5037fa 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -10,8 +10,10 @@ from flask_login import LoginManager from flask_babel import Babel from flask_socketio import SocketIO from config import Config +from flask_cors import CORS, cross_origin app = Flask(__name__) +CORS(app) #app.config['BABEL_DEFAULT_LOCALE'] = 'fin' #app.config['BABEL_TRANSLATION_DIRECTORIES'] ='C:/Users/Timo/git/pet-rating/app/translations' diff --git a/app/experiment/templates/add_embody.html b/app/experiment/templates/add_embody.html new file mode 100644 index 0000000000000000000000000000000000000000..535fb830e4340b5cacaa79a2e660ec0002b141a4 --- /dev/null +++ b/app/experiment/templates/add_embody.html @@ -0,0 +1,32 @@ +{% extends "base.html" %} +{% block content %} + +<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> + +{% from "_formhelpers.html" import render_field %} + +<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> + </div> + + <div class="custom-file"> + <input type="file" id="embody-file-input" class="custom-file-input" name="picture" accept="image/*" > + <label class="custom-file-label" for="upload_file">Choose file</label> + </div> + + <hr> + + <button type="submit" class="btn btn-primary submit-file" disabled>Submit</button> + <a class="btn btn-primary" href="{{ request.referrer }}" role="button">Cancel</a> +</form> + +<script src="{{ url_for('static', filename='js/enable_button.js') }}" ></script> + +{% endblock %} diff --git a/app/experiment/templates/experiment_statistics.html b/app/experiment/templates/experiment_statistics.html index 4a4c79e67145f368ff50dbe94dde8a5c5d2d2159..978d97c7777bb0502735542b4560339a6724fd38 100644 --- a/app/experiment/templates/experiment_statistics.html +++ b/app/experiment/templates/experiment_statistics.html @@ -46,8 +46,6 @@ {% endfor %} - - <h1 class="container mt-5 display-4 text-left"><br>Rating task question headers:</h1> <br> <table class="table"> @@ -76,39 +74,41 @@ <table class="table"> <thead> <tr> - <th scope="col" nowrap>Stimulus ID:</td> - <th scope="col" nowrap>Stimulus:</th> - <th scope="col" nowrap>Embody:</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 %} - - <tr> - <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> + {% for embody_picture in embody_questions %} + <tr> + + {% if s.type == 'text' %} + <td>{{ s.text }}</td> + {% else %} + <td>{{ s.media }}</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"> +<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%"> <!-- Creating image... --> </div> diff --git a/app/experiment/templates/view_experiment.html b/app/experiment/templates/view_experiment.html index 472a0760e755418186db9260264e4051135e665e..41fec30f3171d8d896f458247d295a2699f75daf 100644 --- a/app/experiment/templates/view_experiment.html +++ b/app/experiment/templates/view_experiment.html @@ -212,7 +212,7 @@ -<h1 class="container mt-5 display-4 text-left"><br>Rating set:</h1> +<h1 class="container mt-5 display-4 text-left"><br>Add embody tool:</h1> <table class="table"> <colgroup> @@ -221,18 +221,37 @@ <col style="width:33%;%"> </colgroup> <tr> - <td>Add embody tool:</td> + <td>Embody tool:</td> <td> - {% if experiment_info[0].embody_enabled %} + + {% if embody_pictures|length > 0 and experiment_info[0].embody_enabled %} Enabled {% else %} Disabled {% endif %} </td> - <td><a class="btn btn-primary btn-block btn-sm btn-info" href="{{ url_for('experiment.set_embody', exp_id=exp_id) }}" role="button">Enable embody</a></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><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> + + {% endfor %} + </table> +<hr> + +<h1 class="container mt-5 display-4 text-left"><br>Add questions:</h1> + <hr> <br> {% for category in categories1 %} diff --git a/app/experiment/views.py b/app/experiment/views.py index 7fb547d02feb075e526c18683dacd6f27da52797..d6a6344a4be24f93f8403e54f4f67756d51bb82f 100644 --- a/app/experiment/views.py +++ b/app/experiment/views.py @@ -28,13 +28,14 @@ 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.models import embody_answer, embody_question from app.forms import ( CreateBackgroundQuestionForm, CreateQuestionForm, UploadStimuliForm, EditBackgroundQuestionForm, EditQuestionForm, EditExperimentForm, UploadResearchBulletinForm, - EditPageForm, RemoveExperimentForm, GenerateIdForm + EditPageForm, RemoveExperimentForm, GenerateIdForm,CreateEmbodyForm ) +from werkzeug import secure_filename #Stimuli upload folder setting #APP_ROOT = os.path.dirname(os.path.abspath(__file__)) @@ -44,6 +45,9 @@ experiment_blueprint = Blueprint("experiment", __name__, #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 @@ -53,37 +57,44 @@ def view(): exp_id = request.args.get('exp_id', None) media = page.query.filter_by(experiment_idexperiment=exp_id).all() - ## TODO: mtype can be multiple! - ## is this even necessary anymore? + # 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 questions_and_options = {} - questions = background_question.query.filter_by(experiment_idexperiment=exp_id).all() + questions = background_question.query.filter_by( + experiment_idexperiment=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 - 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 - questions1 = questions_and_options - + #sliderset categories_and_scales = {} categories = question.query.filter_by(experiment_idexperiment=exp_id).all() - for cat in categories: - + for cat in categories: + scale_list = [(cat.left, cat.right)] - categories_and_scales[cat.idquestion, cat.question] = scale_list - + categories_and_scales[cat.idquestion, cat.question] = scale_list + categories1 = categories_and_scales - return render_template('view_experiment.html', exp_id=exp_id, media=media, mtype=mtype, experiment_info=experiment_info, categories1=categories1, questions1=questions1) + # embody pictures + embody_pictures = embody_question.query.filter_by( + experiment_idexperiment=exp_id).all() + + return render_template('view_experiment.html', exp_id=exp_id, media=media, mtype=mtype, experiment_info=experiment_info, categories1=categories1, questions1=questions1, embody_pictures=embody_pictures) # Experiment info: @@ -121,50 +132,37 @@ def remove(): #Tables remove_forced_id = forced_id.query.filter_by(experiment_idexperiment=exp_id).all() - - for b in range(len(remove_forced_id)): - db.session.delete(remove_forced_id[b]) - db.session.commit() + 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() - #cycle through all bg questions and delete their options - 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() - - for b in range(len(remove_background_question_option)): - - db.session.delete(remove_background_question_option[b]) - db.session.commit() - #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_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() - - for b in range(len(remove_background_question_answers)): - db.session.delete(remove_background_question_answers[b]) - db.session.commit() - + 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() - for a in range(len(remove_question)): - remove_question_answers = answer.query.filter_by(question_idquestion=remove_question[a].idquestion).all() - - for b in range(len(remove_question_answers)): - db.session.delete(remove_question_answers[b]) - db.session.commit() - + remove_rows(remove_question_answers) db.session.delete(remove_question[a]) db.session.commit() - + + # Remove experiment embody pictures, questions and answers + 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_rows(remove_embody_questions) + #Remove all pages and datafiles remove_pages = page.query.filter_by(experiment_idexperiment=exp_id).all() @@ -187,11 +185,7 @@ def remove(): 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() - - for b in range(len(remove_trial_randomizations)): - db.session.delete(remove_trial_randomizations[b]) - db.session.commit() - + remove_rows(remove_trial_randomizations) db.session.delete(remove_answer_set[a]) db.session.commit() @@ -498,16 +492,12 @@ def remove_bg_question(): else: - # foreign key constraint fails!!! - # TODO: remove background_question_answer, background_question_options - # before removing background question 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)): db.session.delete(remove_options[a]) db.session.commit() - remove_question = background_question.query.filter_by(idbackground_question=remove_id).first() @@ -519,22 +509,70 @@ def remove_bg_question(): -# Rating set sliders/embody: - @experiment_blueprint.route('/set_embody') @login_required 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.embody_enabled = (True if exp.embody_enabled == False else False) - db.session.commit() - return redirect(url_for('experiment.view', exp_id=exp_id)) +@experiment_blueprint.route('/add_embody', methods=['GET', 'POST']) +@login_required +def add_embody(): + + exp_id = request.args.get('exp_id', None) + default = request.args.get('default', None) + exp_info = experiment.query.filter_by(idexperiment=exp_id).first() + + 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) + + if request.method == 'POST' and form.validate(): + picture = request.files.get("picture") + question = request.form.get("question") + + # get filename + filename = secure_filename(picture.filename) + path = 'static/embody_images/' + str(exp_id) + db_path = '/' + path + str('/') + str(filename) + target = os.path.join(APP_ROOT, path) + + # create folder with experiment id (if it does not exist) + if not os.path.isdir(target): + os.mkdir(target) + + # save file to server + 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) + db.session.add(new_embody) + exp_info.embody_enabled = 1 + db.session.commit() + + + return redirect(url_for('experiment.view', exp_id=exp_id)) + + return render_template('add_embody.html', form=form) + + @experiment_blueprint.route('/add_questions', methods=['GET', 'POST']) @login_required def add_questions(): @@ -606,9 +644,43 @@ def remove_question(): remove_id = request.args.get('idquestion', None) 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() + 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)) + + +@experiment_blueprint.route('/remove_embody') +@login_required +def remove_embody(): + + # TODO: if len(embody_questions) == 0: + # set embody_enabled to False (in experiment) + + 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)) - # TODO: foreign key constraint fails!!! - # answers has reference to question -> remove answers first + else: + remove_id = request.args.get('idembody', None) + 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_rows(remove_answers) db.session.delete(remove_question) db.session.commit() @@ -772,17 +844,30 @@ def remove_stimuli(): target = os.path.join(APP_ROOT, remove_page.media) os.remove(target) - # TODO: foreign key constraint fails!!! - # answers has reference to page -> remove answers first + # remove slider answer from this page + 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_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': - # TODO: foreign key constraint fails!!! - # answers has reference to page -> remove answers first + # remove slider answer from this page + 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_rows(remove_embody_answers) + db.session.delete(remove_page) db.session.commit() @@ -832,21 +917,30 @@ def statistics(): #Background question answers - bg_questions = background_question.query.filter_by(experiment_idexperiment=exp_id).all() + 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 - #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() - - 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) + 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) import embody_plot @@ -881,8 +975,9 @@ def create_embody(): @socketio.on('draw', namespace="/create_embody") def create_embody(page_id): page = page_id["page"] + embody = page_id["embody"] - img_path = embody_plot.get_coordinates(page) + img_path = embody_plot.get_coordinates(page, embody) print(img_path) emit('end', {'path':img_path}) @@ -893,4 +988,11 @@ def create_embody(): emit('end', {'connection': 'off'}) ''' + +def remove_rows(rows): + """Remove list of rows from database""" + for a in range(len(rows)): + db.session.delete(rows[a]) + db.session.commit() + # EOF diff --git a/app/forms.py b/app/forms.py index ff4bbb91ae6a4ad663ba3e7e496436ea2f8bab5a..7d9632c1219279ed4482508d5655925778b10879 100644 --- a/app/forms.py +++ b/app/forms.py @@ -141,6 +141,12 @@ class CreateQuestionForm(Form): submit = SubmitField('Send') +class CreateEmbodyForm(Form): + + question = TextAreaField('Question') + picture = FileField('Upload picture') + submit = SubmitField('Send') + class EditQuestionForm(Form): left = StringField('left_scale', [validators.DataRequired()]) diff --git a/app/models.py b/app/models.py index 9c3fe4d661adc59060414199cca7733422df4fa6..6920ffaa20a1c611e9d837b5c8985d944b5410db 100644 --- a/app/models.py +++ b/app/models.py @@ -50,7 +50,7 @@ class experiment (db.Model): stimulus_size = db.Column(db.String(120)) 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=True) + 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) @@ -107,6 +107,17 @@ class question (db.Model): 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')) + 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 ) + + class page (db.Model): __tablename__ = "page" idpage = db.Column(db.Integer, primary_key=True) @@ -136,10 +147,11 @@ class embody_answer (db.Model): idanswer = db.Column(db.Integer, primary_key=True) 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')) coordinates = db.Column(db.Text) def __repr__(self): - return "<idanswer = '%s', answer_set_idanswer_set = '%s', coordinates = '%s', page_idpage = '%s'>" % (self.idanswer, self.answer_set_idanswer_set, self.coordinates, self.page_idpage) + 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) class trial_randomization (db.Model): diff --git a/app/routes.py b/app/routes.py index 67314d65e2877eece00dd9a251d01250c9128bb9..9a58a83df99bbbe0113869948f8ee0043f097a7f 100644 --- a/app/routes.py +++ b/app/routes.py @@ -5,6 +5,8 @@ import os import random import secrets from datetime import datetime +import tempfile +import json from flask import ( Flask, @@ -14,7 +16,8 @@ from flask import ( flash, redirect, url_for, - Blueprint + Blueprint, + send_file ) from sqlalchemy import and_ @@ -24,7 +27,7 @@ from flask_babel import Babel, _, lazy_gettext as _l from app import app, db, babel from app.models import background_question, experiment from app.models import background_question_answer -from app.models import page, question +from app.models import page, question, embody_question, embody_answer from app.models import background_question_option from app.models import answer_set, answer, forced_id from app.models import user, trial_randomization @@ -340,18 +343,84 @@ def view_research_notification(): def download_csv(): exp_id = request.args.get('exp_id', None) - - """ - with open('export_new.csv', 'w', newline='') as f: - - thewriter = csv.writer(f) - - thewriter.writerow(['1','2','3','4']) - thewriter.writerow(['a','b','c','d']) - """ - - return redirect(url_for('experiment.statistics', exp_id=exp_id)) + 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 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 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 for count,question in enumerate(embody_questions, 1)]) + + csv += header + '\r\n' + + answer_row = '' + + for participant in participants: + + # append user session id + answer_row += participant.session + ';' + + # TODO: + # 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) + ';' + + # append embody answers (coordinates) + embody_answers = embody_answer.query.filter_by(answer_set_idanswer_set=participant.idanswer_set).all() + 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) + + 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') + + finally: + os.remove(path) + @app.route('/researcher_info') @login_required diff --git a/app/static/css/main.css b/app/static/css/main.css index c6fc26330722748ff114cc9d29d1052bfa22e7bf..c9296490b9522ef7efd00054efc0567fa27a87b8 100644 --- a/app/static/css/main.css +++ b/app/static/css/main.css @@ -57,4 +57,9 @@ body { background-color:rgb(248, 249, 250); -webkit-box-shadow: none; box-shadow: none; - } +} + + +.thumbnail { + max-height: 100px; +} diff --git a/app/static/js/canvas.js b/app/static/js/canvas.js index e7f08018698b08ffe42edfa68ec1e7769d251dbb..c7a1e72b59841614afa288efd0bc05e103eb23d3 100644 --- a/app/static/js/canvas.js +++ b/app/static/js/canvas.js @@ -1,26 +1,10 @@ const url = 'http://127.0.0.1:8000/'; +const defaultEmbodySource = '/static/img/dummy_600.png'; $(document).ready(function()Â { - try { - var canvas = $("#embody-canvas") - var canvasInfo = $(".canvas-info") - var context = document.getElementById("embody-canvas").getContext("2d"); - var img = document.getElementById("baseImage"); - - $(img).on('load', function() { - drawBaseImage() - }) - - } catch (e) { - console.log(e) - if (e instanceof TypeError) { - return - } - } - // Init draw variables /* OO-style coordinates: @@ -37,7 +21,45 @@ $(document).ready(function()Â { var clickRadius = new Array(); var clickDrag = new Array(); var paint; - var drawRadius=13; + var drawRadius=13 + var default_embody=false + var points = [] + var imageId = null; + + try { + var canvas = $("#embody-canvas") + var canvasInfo = $(".canvas-info") + var context = document.getElementById("embody-canvas").getContext("2d"); + + //var oldimg = document.getElementById("baseImage"); + var img = $('.embody-image.selected-embody')[0] + + $(img).on('load', function() { + drawImage() + }) + + } catch (e) { + console.log(e) + if (e instanceof TypeError) { + return + } + } + + function drawImage() { + + newImage = new Image(); + newImage.src = img.src + imageId = img.id + + var width = newImage.width; + var height = newImage.height; + + context.canvas.height = height + context.canvas.width = width + + context.drawImage(newImage, 0, 0); + $(img).hide() + } // Click handlers canvas.mousedown(function(e){ @@ -112,6 +134,41 @@ $(document).ready(function()Â { }) + function saveData()Â { + + points.push({ + id: imageId, + x: clickX, + y: clickY, + r: clickRadius + }) + + clickX = [] + clickY = [] + clickRadius = [] + + if ($(img).hasClass('last-embody')) { + // Send data to db + try { + points = JSON.stringify(points) + $("#canvas-data").val(points); + $("#canvas-form").submit(); + } catch(e) { + console.log(e) + } + + } else { + // Show next picture + img = img.nextElementSibling + + try { + drawImage() + } catch(e) { + console.log(e) + } + } + } + // Draw methods function addClick(x, y, dragging=false) { clickX.push(x); @@ -132,13 +189,18 @@ $(document).ready(function()Â { // Method for checking if cursor is inside human body before creating brush stroke function pointInsideBaseImage(point) { - var imageData = context.getImageData(point[0], point[1],1,1) - startR = imageData.data[0]; - startG = imageData.data[1]; - startB = imageData.data[2]; + if (default_embody) { + var imageData = context.getImageData(point[0], point[1],1,1) - return (isWhite(startB) && isWhite(startG) && isWhite(startR)) ? false : true; + startR = imageData.data[0]; + startG = imageData.data[1]; + startB = imageData.data[2]; + + return (isWhite(startB) && isWhite(startG) && isWhite(startR)) ? false : true; + } else { + return true + } } function redraw() { @@ -159,13 +221,23 @@ $(document).ready(function()Â { // Gradient: var gradient = context.createRadialGradient(lastX, lastY, 1, lastX, lastY, drawRadius); + + gradient.addColorStop(0, "rgba(255,0,0,1)"); gradient.addColorStop(1, "rgba(255,0,0,0.1)"); + + context.fillStyle = gradient // Draw circle with gradient drawPoint(lastX, lastY, drawRadius) - drawMaskToBaseImage() + + + // Draw mask on default image + + if (img.getAttribute('src') === defaultEmbodySource) { + drawMaskToBaseImage() + } } @@ -196,19 +268,8 @@ $(document).ready(function()Â { clickDrag = [] } - function saveData()Â { - - var points = { - x: clickX, - y: clickY, - r: clickRadius - } - - points = JSON.stringify(points) - $("#canvas-data").val(points); - $("#canvas-form").submit(); - } - drawBaseImage() + drawImage() + }); \ No newline at end of file diff --git a/app/static/js/enable_button.js b/app/static/js/enable_button.js new file mode 100644 index 0000000000000000000000000000000000000000..b5c1da789597aa8300b3dfe12e9ab278d456d158 --- /dev/null +++ b/app/static/js/enable_button.js @@ -0,0 +1,13 @@ + + + +$( document ).ready( function() { + $('#embody-file-input').change(function(filename) { + + var fileName = $(this).val(); + //replace the "Choose a file" label + $(this).next('.custom-file-label').html(fileName); + + $('.submit-file').prop("disabled", false); + }) +}) diff --git a/app/static/js/getDrawing.js b/app/static/js/getDrawing.js index b3159fafb9d0dae032667a48f3d57f527c925e06..008ae4e9733706be53aac9b14a7c9ae9d3f0f627 100644 --- a/app/static/js/getDrawing.js +++ b/app/static/js/getDrawing.js @@ -44,10 +44,13 @@ $(document).ready(function()Â { initConnection(socket) // - var pageId = this.dataset.value - socket.emit('draw', {page:pageId}) + 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) @@ -68,4 +71,10 @@ $(document).ready(function()Â { }) + function scrollTo(hash) { + $('html, body').animate({ + 'scrollTop': $('#'+hash).offset().top - 250 + }, 500); + } + }) \ No newline at end of file diff --git a/app/task/templates/task.html b/app/task/templates/task.html index d262f391666eb1c459feeb781a46e9501e89820d..39fd4e9df4f4c5a2aa184a32bfdde60c49925eb9 100644 --- a/app/task/templates/task.html +++ b/app/task/templates/task.html @@ -94,18 +94,31 @@ <!-- Select form type --> - {{page_num}} + + page num: {{page_num}} <br> {% 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> </div> - <img id="baseImage" class="" src={{ url_for('static', filename='img/dummy_600.png') }} /> + + {% 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 %} + <img id="baseImageMask" class="hidden" src={{ url_for('static', filename='img/dummy_600_mask.png') }} /> - <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"> @@ -126,6 +139,8 @@ </div> </form> +<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"> @@ -158,7 +173,5 @@ </form> {% endif %} - -<script src="{{ url_for('static', filename='js/canvas.js') }}" ></script> {% endblock %} \ No newline at end of file diff --git a/app/task/views.py b/app/task/views.py index a5f084dad5f4eb5d81c0006448f9f2dd9431f1ff..c6ae884b8bb3e4e1d5841aec4dd45ac0caf41384 100644 --- a/app/task/views.py +++ b/app/task/views.py @@ -15,6 +15,7 @@ from flask import ( url_for, Blueprint ) +from flask_cors import CORS,cross_origin from sqlalchemy import and_ from sqlalchemy import exc @@ -23,7 +24,7 @@ from flask_babel import _, lazy_gettext as _l from app import db from app.models import experiment from app.models import page, question -from app.models import answer_set, answer, embody_answer +from app.models import answer_set, answer, embody_answer, embody_question from app.models import user, trial_randomization from app.forms import Answers, TaskForm, ContinueTaskForm, StringForm @@ -46,9 +47,9 @@ def get_randomized_page(page_id): def add_slider_answer(key, value, page_id=None): - '''Insert slider value to database. If trial randomization is set to 'Off' + """Insert slider value to database. If trial randomization is set to 'Off' the values are inputted for session['current_idpage']. Otherwise the values - are set for the corresponding id found in the trial randomization table''' + are set for the corresponding id found in the trial randomization table""" participant_answer = answer(question_idquestion=key, answer_set_idanswer_set=session['answer_set'], answer=value, page_idpage=page_id) db.session.add(participant_answer) @@ -149,8 +150,9 @@ def slider_on(): @task_blueprint.route('/embody/<int:page_num>', methods=['POST']) +@cross_origin() def task_embody(page_num): - '''Save embody drawing to database''' + '''Save embody drawing to database.''' form = StringForm(request.form) pages = page.query.filter_by(experiment_idexperiment=session['exp_id']).paginate(per_page=1, page=page_num, error_out=True) @@ -168,13 +170,22 @@ def task_embody(page_num): # Add answer to DB if check_answer is None: - # Add new embody answer - participant_answer = embody_answer(answer_set_idanswer_set=session['answer_set'], coordinates=data['coordinates'], page_idpage=page_id) - db.session.add(participant_answer) - db.session.commit() + for coordinate_data in coordinates: + + idembody = int(coordinate_data['id'].split('-')[1]) + del coordinate_data['id'] + del coordinate_data['r'] + + participant_answer = embody_answer( + answer_set_idanswer_set=session['answer_set'], coordinates=json.dumps(coordinate_data), page_idpage=page_id, embody_question_idembody=idembody) + db.session.add(participant_answer) + db.session.commit() + else: flash("Page has been answered already. Answers discarded") + + # Check if there are unanswered slider questions -> if true redirect to same page if slider_on(): update_answer_set_type('slider') @@ -210,7 +221,7 @@ def task_answer(page_num): if embody_on(): update_answer_set_type('embody') - # Always redirect to next page from sliders + # Always redirect to next page(stimulus) from sliders update_answer_set_page() return next_page(pages) @@ -237,8 +248,49 @@ def task(page_num): randomized_page_id = get_randomized_page(page_id).randomized_idpage randomized_stimulus = page.query.filter_by(idpage=randomized_page_id).first() - + + # get all embody questions + + embody_questions = embody_question.query.filter_by(experiment_idexperiment=session['exp_id']).all() + + + + print(embody_questions) print(session) + + ''' + + [ + <idembody = '7', experiment_idexperiment = '5', picture = '/static/embody_images/5/madam-300x250.jpg', question = 'Color the ares where human emotions can be found'>, + <idembody = '14', experiment_idexperiment = '5', picture = '/static/embody_images/5/onni_LOGO-RGB-transparent_bg_cut.png', question = 'dfDFADFADSFDFAS'>, + <idembody = '16', experiment_idexperiment = '5', picture = '/static/img/dummy_600.png', question = 'Color the regions whose activity you feel increasing or getting stronger'>] + + <SecureCookieSession { + '_fresh': True, + '_id': '64d8cf95fb694f63a0b4ffdfbdfde4b608fdcabd374869510101345cf3ce527576ba7b2c4b6ed24315eeb23eac573309ab75f22121d5618d3048479552b948e4', + 'answer_set': 52, + 'csrf_token': 'ab1381281c440e95afc3ef6c0c69712c8c64173a', + 'current_idpage': 13, + 'exp_id': '5', + 'language': 'English', + 'randomization': 'Off', + 'type': 'text', + 'user': 'f693e4', + 'user_id': '1' + }> + + + from session['current_idpage'] system gets CURRENT stimulant (page) + + in select_from_type() the CURRENT question type is selected (embody/sliders) + + + + ''' + + # TODO: show embody tool when its disabled!!!! + + return render_template( 'task.html', pages=pages, @@ -249,7 +301,8 @@ def task(page_num): rating_instruction=experiment_info.single_sentence_instruction, stimulus_size=stimulus_size, stimulus_size_text=stimulus_size_text, - experiment_info=experiment_info + experiment_info=experiment_info, + embody_questions=embody_questions ) diff --git a/create_rating_db.sql b/create_rating_db.sql index 40516a890ad16c1350eee58667042f9e00f4f91f..0e34f82716d3f29393c545e43448f669b977e05c 100644 --- a/create_rating_db.sql +++ b/create_rating_db.sql @@ -13,12 +13,14 @@ DROP TABLE IF EXISTS forced_id; DROP TABLE IF EXISTS user; DROP TABLE IF EXISTS trial_randomization; DROP TABLE IF EXISTS embody_answer; +DROP TABLE IF EXISTS embody_question; DROP TABLE IF EXISTS answer; DROP TABLE IF EXISTS answer_set; DROP TABLE IF EXISTS question; DROP TABLE IF EXISTS page; DROP TABLE IF EXISTS experiment; +/* Experiment set */ CREATE TABLE experiment ( idexperiment INTEGER NOT NULL AUTO_INCREMENT, name VARCHAR(120), @@ -39,9 +41,6 @@ CREATE TABLE experiment ( PRIMARY KEY (idexperiment) ); -/* New fields for updating embody tool to onni.utu.fi */ -ALTER TABLE experiment ADD COLUMN (embody_enabled BOOLEAN DEFAULT 0); - /* Answer set holds session information about users experiment */ CREATE TABLE answer_set ( idanswer_set INTEGER NOT NULL AUTO_INCREMENT, @@ -55,10 +54,6 @@ CREATE TABLE answer_set ( FOREIGN KEY(experiment_idexperiment) REFERENCES experiment (idexperiment) ); -ALTER TABLE answer_set ADD COLUMN (answer_type VARCHAR(120)); - -/* TODO: Update answer_set so it knows which part of the page the user is doing (embody/sliders/something else) */ - /* Background questions are asked before the experiment begins */ CREATE TABLE background_question ( idbackground_question INTEGER NOT NULL AUTO_INCREMENT, @@ -146,18 +141,6 @@ 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,..], r:[13,13,8,...]} */ -CREATE TABLE embody_answer ( - idanswer INTEGER NOT NULL AUTO_INCREMENT, - answer_set_idanswer_set INTEGER, - page_idpage INTEGER, - coordinates TEXT, - PRIMARY KEY (idanswer), - FOREIGN KEY(answer_set_idanswer_set) REFERENCES answer_set (idanswer_set), - FOREIGN KEY(page_idpage) REFERENCES page (idpage) -); - /* Create indexes for faster operations */ CREATE INDEX ix_experiment_consent_text ON experiment (consent_text(255)); CREATE INDEX ix_experiment_creation_time ON experiment (creation_time); @@ -174,3 +157,37 @@ CREATE INDEX ix_page_media ON page (media); CREATE INDEX ix_page_text ON page (text); CREATE INDEX ix_page_type ON page (type); + +/* New fields for updating embody tool to onni.utu.fi */ + +/* 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, + page_idpage INTEGER, + embody_question_idembody INTEGER, + coordinates TEXT, + PRIMARY KEY (idanswer), + FOREIGN KEY(answer_set_idanswer_set) REFERENCES answer_set (idanswer_set), + FOREIGN KEY(page_idpage) REFERENCES page (idpage) , + FOREIGN KEY(embody_question_idembody) REFERENCES embody_question (idembody) +); + +/* Embody picture/question information */ +CREATE TABLE embody_question ( + idembody INTEGER NOT NULL AUTO_INCREMENT, + experiment_idexperiment INTEGER, + picture TEXT, + question TEXT, + PRIMARY KEY (idembody), + FOREIGN KEY(experiment_idexperiment) REFERENCES experiment (idexperiment) +); + +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); + +ALTER TABLE experiment ADD COLUMN (embody_enabled BOOLEAN DEFAULT 0); /*this might be unnecessary*/ +ALTER TABLE answer_set ADD COLUMN (answer_type VARCHAR(120)); + +/* TODO: Update answer_set so it knows which part of the page the user is doing (embody/sliders/something else) */ \ No newline at end of file diff --git a/embody_plot.py b/embody_plot.py index 90e92fddbddb8424f7f9b3029e598be906f80df2..d8c8634699fdb0510bb86f0bf5b22d1717cfab4d 100644 --- a/embody_plot.py +++ b/embody_plot.py @@ -37,12 +37,15 @@ import matplotlib.image as mpimg from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas from matplotlib.figure import Figure +from flask_socketio import emit +from app import socketio + # Hard coded image size WIDTH = 207 HEIGHT = 600 # image paths -IMAGE_PATH = './app/static/img/dummy_600.png' +DEFAULT_IMAGE_PATH = './app/static/img/dummy_600.png' IMAGE_PATH_MASK = './app/static/img/dummy_600_mask.png' STATIC_PATH = './app/static/' @@ -54,15 +57,8 @@ 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' +SELECT_BY_PAGE_AND_PICTURE = 'select coordinates from embody_answer where page_idpage=%s and embody_question_idembody=%s' -''' -mariadb_connection = mariadb.connect( - user='rating', - password='rating_passwd', - database='rating_db' -) -''' - # Get date now = datetime.datetime.now() DATE_STRING = now.strftime("%Y-%m-%d") @@ -81,7 +77,6 @@ class MyDB(object): self._db_connection.close() - def matlab_style_gauss2D(shape=(1,1),sigma=5): """2D gaussian mask - should give the same result as MATLAB's fspecial('gaussian',[shape],[sigma])""" @@ -118,21 +113,30 @@ def timeit(method): @timeit -def get_coordinates(selected_value, select_clause=SELECT_BY_PAGE): +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""" db = MyDB() - db.query(select_clause, (selected_value,)) + db.query(select_clause, (idpage,idembody)) # Get coordinates coordinates = format_coordinates(db._db_cur) - # Draw image - plt = plot_coordinates(coordinates) + if idembody: + # Get image path + image_query = db.query('SELECT picture from embody_question where idembody=%s', (idembody,)) + 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) + # Save image to ./app/static/ - img_filename = 'PAGE-' + str(selected_value) + '-' + DATE_STRING + '.png' + img_filename = 'PAGE-' + str(idpage) + '-' + DATE_STRING + '.png' plt.savefig(STATIC_PATH + img_filename) # Return image path to function caller @@ -148,6 +152,7 @@ def format_coordinates(cursor): # 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']) @@ -164,17 +169,15 @@ def format_coordinates(cursor): "coordinates":list(map(map_coordinates, x,y,r)) } -from flask_socketio import emit -from app import socketio -def plot_coordinates(coordinates): +def plot_coordinates(coordinates, image_path=DEFAULT_IMAGE_PATH): # Total amount of points points_count = len(coordinates['coordinates']) # Load image to a plot - image = mpimg.imread(IMAGE_PATH) - image_mask = mpimg.imread(IMAGE_PATH_MASK) + image = mpimg.imread(image_path) + image_data = image.shape # Init plots fig, (ax1, ax2) = plt.subplots(nrows=1, ncols=2) @@ -187,7 +190,9 @@ def plot_coordinates(coordinates): # 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)) + + # set height/width from image + frame = np.zeros((image_data[0] + 10,image_data[1] + 10)) for idx, point in enumerate(coordinates["coordinates"]): frame[point[1], point[0]] = 1 @@ -201,23 +206,14 @@ def plot_coordinates(coordinates): except RuntimeError as err: continue - 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) - ''' + if image_path == DEFAULT_IMAGE_PATH: + image_mask = mpimg.imread(IMAGE_PATH_MASK) + ax2.imshow(image_mask) + else: + # TODO: gaussian disk appearing only on empty spaces in the pictures + # -> at the moment this implementation works only for the default image + # with pre-created image mask (IMAGE_PATH_MASK) + ax2.imshow(image) # return figure for saving/etc... return fig @@ -236,7 +232,6 @@ def plot_coordinates(coordinates): plt.show() ''' - if __name__=='__main__': arg_parser = argparse.ArgumentParser(description='Draw bodily maps of emotions') @@ -248,11 +243,11 @@ if __name__=='__main__': value = args['integers'][0] if args['stimulus']: - get_coordinates(value, SELECT_BY_PAGE) + get_coordinates(value, None, SELECT_BY_PAGE) elif args['experiment']: - get_coordinates(value, SELECT_BY_EXP_ID) + get_coordinates(value, None, SELECT_BY_EXP_ID) elif args['answer_set']: - get_coordinates(value, SELECT_BY_ANSWER_SET) + get_coordinates(value, None, SELECT_BY_EXP_ID) else: print("No arguments given. Exit.") sys.exit(0)