From 5e711f079121e0b9e958261df4aafcf568cb62cf Mon Sep 17 00:00:00 2001 From: Gertjan van den Burg Date: Tue, 26 Mar 2019 13:35:17 +0000 Subject: First working version of complete annotation functionality --- app/main/routes.py | 68 +++++++++- app/models.py | 3 +- app/static/annotate.css | 3 + app/templates/annotate/index.html | 139 ++++++++++++++++++++- app/templates/index.html | 2 +- .../878e8193ae4d_change_annotation_structure.py | 31 +++++ 6 files changed, 233 insertions(+), 13 deletions(-) create mode 100644 migrations/versions/878e8193ae4d_change_annotation_structure.py diff --git a/app/main/routes.py b/app/main/routes.py index a1d1a63..f1f7a15 100644 --- a/app/main/routes.py +++ b/app/main/routes.py @@ -1,12 +1,29 @@ # -*- coding: utf-8 -*- -from flask import render_template, flash, url_for, redirect +import datetime + +from flask import render_template, flash, url_for, redirect, request from flask_login import current_user, login_required +from app import db from app.main import bp -from app.models import Task +from app.models import Annotation, Task from app.main.datasets import load_data_for_chart +RUBRIC = """ +Please mark all the points in the time series where an abrupt change in + the behaviour of the series occurs. +
+
+If there are no such points, please click the "no changepoints" button. +
+When you're ready, please click the submit button. +
+Note: You can zoom and pan the graph if needed. +
+Thank you! +""" + @bp.route("/") @bp.route("/index") @@ -28,10 +45,53 @@ def index(): @bp.route("/annotate/", methods=("GET", "POST")) @login_required def task(task_id): + if request.method == "POST": + # record post time + now = datetime.datetime.utcnow() + + # get the json from the client + annotation = request.get_json() + if annotation["task"] != task_id: + flash("Internal error: task id doesn't match.") + return redirect(url_for(task_id=task_id)) + + task = Task.query.filter_by(id=task_id).first() + + # remove all previous annotations for this task + for ann in Annotation.query.filter_by(task_id=task_id).all(): + db.session.delete(ann) + task.done = False + task.annotated_on = None + db.session.commit() + + # record the annotation + if annotation["changepoints"] is None: + ann = Annotation(cp_index=None, task_id=task_id) + db.session.add(ann) + db.session.commit() + else: + for cp in annotation["changepoints"]: + ann = Annotation(cp_index=cp["x"], task_id=task_id) + db.session.add(ann) + db.session.commit() + + # mark the task as done + task.done = True + task.annotated_on = now + db.session.commit() + + flash("Your annotation has been recorded, thank you!") + return url_for("main.index") + task = Task.query.filter_by(id=task_id).first() if task is None: flash("No task with id %r has been assigned to you." % task_id) return redirect(url_for("main.index")) data = load_data_for_chart(task.dataset.name) - return render_template("annotate/index.html", title="Annotate %s" % - task.dataset.name, task=task, data=data) + return render_template( + "annotate/index.html", + title="Annotate %s" % task.dataset.name, + task=task, + data=data, + rubric=RUBRIC, + ) diff --git a/app/models.py b/app/models.py index 06dfbc2..03ba884 100644 --- a/app/models.py +++ b/app/models.py @@ -81,8 +81,7 @@ class Task(db.Model): class Annotation(db.Model): id = db.Column(db.Integer, primary_key=True) - time_start = db.Column(db.Integer) - time_end = db.Column(db.Integer) + cp_index = db.Column(db.Integer) task = db.relation("Task") task_id = db.Column(db.Integer, db.ForeignKey("task.id")) diff --git a/app/static/annotate.css b/app/static/annotate.css index 0ee40f6..7dc222f 100644 --- a/app/static/annotate.css +++ b/app/static/annotate.css @@ -19,3 +19,6 @@ rect { opacity: 0; } +#rubric { + text-align: center; +} diff --git a/app/templates/annotate/index.html b/app/templates/annotate/index.html index 1805d95..6a34f61 100644 --- a/app/templates/annotate/index.html +++ b/app/templates/annotate/index.html @@ -7,14 +7,65 @@ {% block app_content %}

{{ task.dataset.name }}

+ +
+
+

{{ rubric | safe }}

+
+
+
-
-
- +
+
+
+
+ + +
+
+
+ + + + +

Selected Changepoints

@@ -22,7 +73,6 @@
- {# Based on: https://github.com/benalexkeen/d3-flask-blog-post/blob/master/templates/index.html #} {# And: https://bl.ocks.org/mbostock/35964711079355050ff1 #} @@ -44,7 +94,7 @@ var svg = d3.select("#graph") .attr("width", divWidth) .attr("height", divHeight); -var margin = {top: 20, right: 20, bottom: 100, left: 100}; +var margin = {top: 20, right: 20, bottom: 50, left: 50}; var width = +svg.attr("width") - margin.left - margin.right; var height = +svg.attr("height") - margin.top - margin.bottom; @@ -155,7 +205,8 @@ function clicked(d, i) { function nozoom() { d3.event.preventDefault(); } - + + + {% endblock %} diff --git a/app/templates/index.html b/app/templates/index.html index 6c6241e..6f868b1 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -17,7 +17,7 @@
{% else %} - No more tasks to do, thank you! + No more annotations to do, thanks for your help, you rock! {% endif %} {% if tasks_done %}

Completed Annotations

diff --git a/migrations/versions/878e8193ae4d_change_annotation_structure.py b/migrations/versions/878e8193ae4d_change_annotation_structure.py new file mode 100644 index 0000000..1b37d5e --- /dev/null +++ b/migrations/versions/878e8193ae4d_change_annotation_structure.py @@ -0,0 +1,31 @@ +"""change annotation structure + +Revision ID: 878e8193ae4d +Revises: 37a7d932fc02 +Create Date: 2019-03-26 12:53:27.069030 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '878e8193ae4d' +down_revision = '37a7d932fc02' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table("annotation") as batch_op: + batch_op.drop_column('time_start') + batch_op.drop_column('time_end') + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('annotation', sa.Column('time_end', sa.INTEGER(), nullable=True)) + op.add_column('annotation', sa.Column('time_start', sa.INTEGER(), nullable=True)) + # ### end Alembic commands ### -- cgit v1.2.3