start anatomy creation forms

This commit is contained in:
Ross
2020-12-06 21:26:23 +00:00
parent ad793cd0af
commit 994d0ec677
10 changed files with 768 additions and 87 deletions
+4
View File
@@ -7,6 +7,7 @@ from .models import (
CidUserAnswer, CidUserAnswer,
Exam, Exam,
Modality, Modality,
Region,
BodyPart, BodyPart,
Structure, Structure,
) )
@@ -42,6 +43,8 @@ class AnatomyAdmin(VersionAdmin):
ExamInline, ExamInline,
] ]
exclude = [ "image_annotations" ]
admin.site.register(AnatomyQuestion, AnatomyAdmin) admin.site.register(AnatomyQuestion, AnatomyAdmin)
admin.site.register(Examination) admin.site.register(Examination)
@@ -49,6 +52,7 @@ admin.site.register(QuestionType)
admin.site.register(CidUserAnswer) admin.site.register(CidUserAnswer)
admin.site.register(Exam) admin.site.register(Exam)
admin.site.register(Modality) admin.site.register(Modality)
admin.site.register(Region)
admin.site.register(BodyPart) admin.site.register(BodyPart)
admin.site.register(Structure) admin.site.register(Structure)
+98 -5
View File
@@ -1,16 +1,109 @@
from django import forms from django.forms import (
Form,
ModelForm,
ModelMultipleChoiceField,
ModelChoiceField,
ChoiceField,
CharField,
)
from django.forms import inlineformset_factory
from .models import CidUserAnswer, AnatomyQuestion from .models import (
Answer,
CidUserAnswer,
AnatomyQuestion,
Examination,
BodyPart,
Structure,
Region,
Modality,
QuestionType,
)
from django.contrib.admin.widgets import FilteredSelectMultiple
from django.forms.widgets import RadioSelect, TextInput
class AnatomyAnswerForm(forms.ModelForm): class AnatomyAnswerForm(ModelForm):
class Meta: class Meta:
model = CidUserAnswer model = CidUserAnswer
fields = ("answer",) fields = ("answer",)
class MarkAnatomyQuestionForm(forms.Form): class MarkAnatomyQuestionForm(Form):
# correct = forms.CharField(required=False) # correct = forms.CharField(required=False)
# half_correct = forms.CharField(required=False) # half_correct = forms.CharField(required=False)
# incorrect = forms.CharField(required=False) # incorrect = forms.CharField(required=False)
marked_answers = forms.CharField(required=False) marked_answers = CharField(required=False)
class AnatomyQuestionForm(ModelForm):
class Media:
# Django also includes a few javascript files necessary
# for the operation of this form element. You need to
# include <script src="/admin/jsi18n"></script>
# in the template.
css = {
"all": ["css/widgets.css"],
}
# Adding this javascript is crucial
js = ["jsi18n.js", "tesseract.min.js"]
def __init__(self, *args, **kwargs):
super(AnatomyQuestionForm, self).__init__(*args, **kwargs)
# self.fields['question'].widget.attrs = {'class': 'question-form', 'rows': 10, 'cols': 100}
# self.fields['feedback'].widget.attrs = {'class': 'feedback-form', 'rows': 10, 'cols': 100}
self.fields["question_type"] = ModelChoiceField(
required=True,
queryset=QuestionType.objects.all(),
initial=1,
)
self.fields["region"] = ModelChoiceField(
required=False,
queryset=Region.objects.all(),
)
self.fields["modality"] = ModelChoiceField(
required=True,
queryset=Modality.objects.all(),
)
self.fields["structure"] = ModelChoiceField(
required=False,
queryset=Structure.objects.all(),
)
self.fields["examination"] = ModelChoiceField(
required=False,
queryset=Examination.objects.all(),
)
self.fields["body_part"] = ModelChoiceField(
required=False,
queryset=BodyPart.objects.all(),
)
class Meta:
model = AnatomyQuestion
fields = ["question_type", "image", "description"]
widgets = {}
AnswerFormSet = inlineformset_factory(
AnatomyQuestion,
Answer,
fields=["answer", "status"],
widgets={
"answer": TextInput(
attrs={"required": "true", "minlength": 2, "maxlength": 500}
)
},
exclude=[],
can_delete=True,
extra=1,
max_num=10,
field_classes="testing",
)
@@ -0,0 +1,26 @@
# Generated by Django 3.1.3 on 2020-12-06 17:16
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('anatomy', '0009_anatomyquestion_image_annotations'),
]
operations = [
migrations.CreateModel(
name='Region',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('region', models.CharField(max_length=200)),
],
),
migrations.AddField(
model_name='anatomyquestion',
name='region',
field=models.ForeignKey(blank=True, help_text='Region the image covers', null=True, on_delete=django.db.models.deletion.SET_NULL, to='anatomy.region'),
),
]
@@ -0,0 +1,20 @@
# Generated by Django 3.1.3 on 2020-12-06 18:12
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('anatomy', '0010_auto_20201206_1716'),
]
operations = [
migrations.AddField(
model_name='anatomyquestion',
name='author',
field=models.ManyToManyField(blank=True, help_text='Author(s) of question', related_name='anatomy_authored_questions', to=settings.AUTH_USER_MODEL),
),
]
+134 -79
View File
@@ -45,6 +45,13 @@ class Structure(models.Model):
return self.structure return self.structure
class Region(models.Model):
region = models.CharField(max_length=200)
def __str__(self):
return self.region
class Modality(models.Model): class Modality(models.Model):
modality = models.CharField(max_length=200) modality = models.CharField(max_length=200)
@@ -63,46 +70,62 @@ class AnatomyQuestion(models.Model):
# author = models.ForeignKey('auth.User', on_delete=models.CASCADE) # author = models.ForeignKey('auth.User', on_delete=models.CASCADE)
# image = models.ImageField() # image = models.ImageField()
# feedback = models.TextField(null=True) # feedback = models.TextField(null=True)
question_type = models.ForeignKey(QuestionType, question_type = models.ForeignKey(
on_delete=models.SET_NULL, QuestionType, on_delete=models.SET_NULL, null=True
null=True) )
image = models.ImageField(upload_to=image_directory_path, image = models.ImageField(upload_to=image_directory_path, storage=image_storage)
storage=image_storage)
image_annotations = models.TextField(blank=True, help_text="Stores a JSON representation of annotations to be applied by cornerstonetools") image_annotations = models.TextField(
blank=True,
help_text="Stores a JSON representation of annotations to be applied by cornerstonetools",
)
description = models.CharField( description = models.CharField(
max_length=400, max_length=400,
help_text= help_text="Short description of the image e.g. 'Sagittal CT Chest, Abdomen & Pelvis', will be displayed as the title.",
"Short description of the image e.g. 'Sagittal CT Chest, Abdomen & Pelvis', will be displayed as the title."
) )
examination = models.ForeignKey(Examination, examination = models.ForeignKey(
on_delete=models.SET_NULL, Examination, on_delete=models.SET_NULL, null=True, blank=True
null=True, )
blank=True) modality = models.ForeignKey(
modality = models.ForeignKey(Modality, Modality,
on_delete=models.SET_NULL, on_delete=models.SET_NULL,
null=True, null=True,
help_text="Modality of the image") help_text="Modality of the image",
body_part = models.ForeignKey(BodyPart, )
on_delete=models.SET_NULL, region = models.ForeignKey(
null=True, Region,
blank=True) on_delete=models.SET_NULL,
structure = models.ForeignKey(Structure, null=True,
on_delete=models.SET_NULL, blank=True,
null=True, help_text="Region the image covers",
blank=True) )
body_part = models.ForeignKey(
BodyPart, on_delete=models.SET_NULL, null=True, blank=True
)
structure = models.ForeignKey(
Structure, on_delete=models.SET_NULL, null=True, blank=True
)
created_date = models.DateTimeField(default=timezone.now) created_date = models.DateTimeField(default=timezone.now)
open_access = models.BooleanField(help_text="If an question should be freely available to browse", open_access = models.BooleanField(
default=True) help_text="If an question should be freely available to browse", default=True
)
author = models.ManyToManyField(
settings.AUTH_USER_MODEL,
blank=True,
help_text="Author(s) of question",
related_name="anatomy_authored_questions",
)
def __str__(self): def __str__(self):
# Get first answer # Get first answer
return "{} [{}, {}]".format(self.GetPrimaryAnswer(), self.modality, return "{} [{}, {}]".format(
self.structure) self.GetPrimaryAnswer(), self.modality, self.structure
)
# Get associated exams # Get associated exams
e = self.exams.all().values_list("name", flat=True) e = self.exams.all().values_list("name", flat=True)
exams = ", ".join(e) exams = ", ".join(e)
@@ -110,18 +133,24 @@ class AnatomyQuestion(models.Model):
if a is None: if a is None:
return "({})".format(exams) return "({})".format(exams)
else: else:
return "{} ({}) [{}, {}]".format(a.answer, exams, self.modality, return "{} ({}) [{}, {}]".format(
self.structure) a.answer, exams, self.modality, self.structure
)
def get_absolute_url(self):
return reverse("anatomy:question_detail", kwargs={"pk": self.pk})
def GetPrimaryAnswer(self): def GetPrimaryAnswer(self):
return self.answers.all().first().answer if len(self.answers.all()) > 0:
return self.answers.all().first().answer
else:
return "None yet..."
def GetExams(self): def GetExams(self):
e = self.exams.all().values_list("name", flat=True) e = self.exams.all().values_list("name", flat=True)
exams = ", ".join(e) exams = ", ".join(e)
return exams return exams
def GetUnmarkedAnswersString(self): def GetUnmarkedAnswersString(self):
unmarked_answers = self.GetUnmarkedAnswers() unmarked_answers = self.GetUnmarkedAnswers()
@@ -129,11 +158,14 @@ class AnatomyQuestion(models.Model):
return "No answers to mark" return "No answers to mark"
return format_html( return format_html(
"<span class='warn'>{} answer unmarked:</span> {}".format( "<span class='warn'>{} answer unmarked:</span> {}".format(
len(unmarked_answers), ", ".join(unmarked_answers))) len(unmarked_answers), ", ".join(unmarked_answers)
)
)
def GetUnmarkedAnswers(self): def GetUnmarkedAnswers(self):
user_answers = set( user_answers = set(
[i.get_compare_string() for i in self.cid_user_answers.all()]) [i.get_compare_string() for i in self.cid_user_answers.all()]
)
unmarked_answers = user_answers - self.GetMarkedAnswers() unmarked_answers = user_answers - self.GetMarkedAnswers()
return unmarked_answers return unmarked_answers
@@ -142,30 +174,40 @@ class AnatomyQuestion(models.Model):
return len(self.GetUnmarkedAnswers()) return len(self.GetUnmarkedAnswers())
def GetMarkedAnswers(self): def GetMarkedAnswers(self):
return set([i.get_compare_string() for i in self.answers.all() if i.status != i.MarkOptions.UNMARKED]) return set(
[
i.get_compare_string()
for i in self.answers.all()
if i.status != i.MarkOptions.UNMARKED
]
)
correct_answers = set([i.answer.lower() for i in self.answers.all()]) correct_answers = set([i.answer.lower() for i in self.answers.all()])
half_mark_answers = set( half_mark_answers = set(
[i.answer.lower() for i in self.half_mark_answers.all()]) [i.answer.lower() for i in self.half_mark_answers.all()]
)
incorrect_answers = set( incorrect_answers = set(
[i.answer.lower() for i in self.incorrect_answers.all()]) [i.answer.lower() for i in self.incorrect_answers.all()]
)
marked_answers = correct_answers | half_mark_answers | incorrect_answers marked_answers = correct_answers | half_mark_answers | incorrect_answers
return marked_answers return marked_answers
class Answer(models.Model): class Answer(models.Model):
question = models.ForeignKey(AnatomyQuestion, question = models.ForeignKey(
related_name="answers", AnatomyQuestion, related_name="answers", on_delete=models.CASCADE
on_delete=models.CASCADE) )
answer = models.CharField(max_length=500) answer = models.CharField(max_length=500)
class MarkOptions(models.TextChoices): class MarkOptions(models.TextChoices):
UNMARKED = '', _('Unmarked') UNMARKED = "", _("Unmarked")
INCORRECT = '0', _('Incorrect') INCORRECT = "0", _("Incorrect")
HALF_MARK = '1', _('Half mark') HALF_MARK = "1", _("Half mark")
CORRECT = '2', _('Correct') CORRECT = "2", _("Correct")
status = models.CharField(max_length=1, choices=MarkOptions.choices, default=MarkOptions.UNMARKED) status = models.CharField(
max_length=1, choices=MarkOptions.choices, default=MarkOptions.UNMARKED
)
def __str__(self): def __str__(self):
return self.answer return self.answer
@@ -178,7 +220,7 @@ class Answer(models.Model):
return self.answer.lower() return self.answer.lower()
#class HalfMarkAnswers(models.Model): # class HalfMarkAnswers(models.Model):
# question = models.ForeignKey(AnatomyQuestion, # question = models.ForeignKey(AnatomyQuestion,
# related_name="half_mark_answers", # related_name="half_mark_answers",
# on_delete=models.CASCADE) # on_delete=models.CASCADE)
@@ -192,7 +234,7 @@ class Answer(models.Model):
# self.answer.strip() # self.answer.strip()
# #
# #
#class IncorrectAnswers(models.Model): # class IncorrectAnswers(models.Model):
# question = models.ForeignKey(AnatomyQuestion, # question = models.ForeignKey(AnatomyQuestion,
# related_name="incorrect_answers", # related_name="incorrect_answers",
# on_delete=models.CASCADE) # on_delete=models.CASCADE)
@@ -208,14 +250,17 @@ class Answer(models.Model):
class Exam(models.Model): class Exam(models.Model):
name = models.CharField(max_length=200) name = models.CharField(max_length=200)
exam_questions = SortedManyToManyField(AnatomyQuestion, exam_questions = SortedManyToManyField(
related_name="exams", blank="true") AnatomyQuestion, related_name="exams", blank="true"
)
active = models.BooleanField(help_text="If an exam should be available", active = models.BooleanField(
default=True) help_text="If an exam should be available", default=True
)
recreate_json = models.BooleanField(help_text="If the json cache needs updating", recreate_json = models.BooleanField(
default=False) help_text="If the json cache needs updating", default=False
)
def __str__(self): def __str__(self):
return self.name return self.name
@@ -224,26 +269,24 @@ class Exam(models.Model):
return self.name return self.name
def get_json_url(self): def get_json_url(self):
return reverse('anatomy:exam_json', args=(self.pk, )) return reverse("anatomy:exam_json", args=(self.pk,))
class UserAnswer(models.Model): class UserAnswer(models.Model):
question = models.ForeignKey(AnatomyQuestion, question = models.ForeignKey(
related_name="user_answers", AnatomyQuestion, related_name="user_answers", on_delete=models.CASCADE
on_delete=models.CASCADE) )
answer = models.CharField(max_length=500, blank=True) answer = models.CharField(max_length=500, blank=True)
user = models.ForeignKey(settings.AUTH_USER_MODEL, user = models.ForeignKey(
null=True, settings.AUTH_USER_MODEL, null=True, blank=True, on_delete=models.SET_NULL
blank=True, )
on_delete=models.SET_NULL)
cid = models.IntegerField(blank=True, null=True) cid = models.IntegerField(blank=True, null=True)
# Each user answer is associated with a particular exam # Each user answer is associated with a particular exam
exam = models.ForeignKey(Exam, exam = models.ForeignKey(
related_name="exam", Exam, related_name="exam", on_delete=models.CASCADE, null=True
on_delete=models.CASCADE, )
null=True)
flagged = models.BooleanField(default=False) flagged = models.BooleanField(default=False)
@@ -255,8 +298,9 @@ class UserAnswer(models.Model):
exam = self.exam exam = self.exam
except (Exam.DoesNotExist, KeyError) as e: except (Exam.DoesNotExist, KeyError) as e:
exam = "None" exam = "None"
return "{}/{}/{}: {}".format(exam, self.cid, self.question.GetPrimaryAnswer(), return "{}/{}/{}: {}".format(
self.answer) exam, self.cid, self.question.GetPrimaryAnswer(), self.answer
)
def clean(self): def clean(self):
if self.answer: if self.answer:
@@ -265,18 +309,18 @@ class UserAnswer(models.Model):
class CidUserAnswer(models.Model): class CidUserAnswer(models.Model):
"""User answers by candidate""" """User answers by candidate"""
question = models.ForeignKey(AnatomyQuestion,
related_name="cid_user_answers", question = models.ForeignKey(
on_delete=models.CASCADE) AnatomyQuestion, related_name="cid_user_answers", on_delete=models.CASCADE
)
answer = models.CharField(max_length=500, blank=True) answer = models.CharField(max_length=500, blank=True)
cid = models.IntegerField(blank=True, null=True) cid = models.IntegerField(blank=True, null=True)
# Each user answer is associated with a particular exam # Each user answer is associated with a particular exam
exam = models.ForeignKey(Exam, exam = models.ForeignKey(
related_name="cid_user_answers", Exam, related_name="cid_user_answers", on_delete=models.CASCADE, null=True
on_delete=models.CASCADE, )
null=True)
created = models.DateTimeField(auto_now_add=True) created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True) updated = models.DateTimeField(auto_now=True)
@@ -286,8 +330,9 @@ class CidUserAnswer(models.Model):
exam = self.exam exam = self.exam
except (Exam.DoesNotExist, KeyError) as e: except (Exam.DoesNotExist, KeyError) as e:
exam = "None" exam = "None"
return "{}/{}/{}: {}".format(exam, self.cid, self.question.GetPrimaryAnswer(), return "{}/{}/{}: {}".format(
self.answer) exam, self.cid, self.question.GetPrimaryAnswer(), self.answer
)
def clean(self): def clean(self):
if self.answer: if self.answer:
@@ -299,9 +344,19 @@ class CidUserAnswer(models.Model):
def get_answer_score(self): def get_answer_score(self):
q = self.question q = self.question
ans = self.answer ans = self.answer
if q.answers.filter(answer__iexact=ans, status=Answer.MarkOptions.CORRECT).first() is not None: if (
q.answers.filter(
answer__iexact=ans, status=Answer.MarkOptions.CORRECT
).first()
is not None
):
mark = 2 mark = 2
elif q.answers.filter(answer__iexact=ans, status=Answer.MarkOptions.HALF_MARK).first() is not None: elif (
q.answers.filter(
answer__iexact=ans, status=Answer.MarkOptions.HALF_MARK
).first()
is not None
):
mark = 1 mark = 1
else: else:
mark = 0 mark = 0
+109
View File
@@ -166,4 +166,113 @@ button a {
#save-annotations { #save-annotations {
position: absolute; position: absolute;
left: 0; left: 0;
}
.drop-target {
border: 1px dashed white;
}
#drop-container {
width: 100%;
width: 100vw;
height: 100px;
text-align: center;
font-size: larger;
position: sticky;
bottom: 0;
left: 0%;
right: 50%;
margin-left: -50vw;
margin-right: -50vw;
}
.submit-button {
position: sticky;
bottom: 0;
}
#feedback-drop-target {
float: right;
height: 100%;
width: 30%;
}
.drop-target-active {
background-color: #427aa1;
}
#drop-filenames span {
font-size: xx-small;
color: gray;
padding-right: 1em;
}
.image-formset {
border: 1px solid white;
}
select, input {
background-color: #05668d;
color: white;
border: none;
}
table.table {
color: white;
}
#view-filter-options {
float: right;
}
.rapid-img {
width: 50%;
}
.feedback-img {
border: 1px solid #05668d;
}
img.uploading {
margin-top: -20px;
height: 75%;
float: right;
}
img.uploading:hover {
height: 95%;
position: fixed;
top: 50%;
left: 50%;
/* bring your own prefixes */
transform: translate(-50%, -50%)
}
.image-ident-warning {
border: 2px solid red;
}
.image-ident-loading {
background: rgba(142, 68, 173, 1);
box-shadow: 0 0 0 0 rgba(142, 68, 173, 1);
animation: pulse-purple 2s infinite;
}
@keyframes pulse-purple {
0% {
transform: scale(0.95);
box-shadow: 0 0 0 0 rgba(142, 68, 173, 0.7);
}
70% {
transform: scale(1);
box-shadow: 0 0 0 10px rgba(142, 68, 173, 0);
}
100% {
transform: scale(0.95);
box-shadow: 0 0 0 0 rgba(142, 68, 173, 0);
}
} }
@@ -0,0 +1,260 @@
{% extends "anatomy/base.html" %}
{% load static %}
{% block js %}
<!--<script type="text/javascript" src="/admin/jsi18n/"></script>-->
<script type="text/javascript">
function showEditPopup(url) {
var win = window.open(url, "Edit",
'height=500,width=800,resizable=yes,scrollbars=yes');
return false;
}
function showAddPopup(triggeringLink) {
var name = triggeringLink.id.replace(/^add_/, '');
href = triggeringLink.href;
var win = window.open(href, name, 'height=500,width=800,resizable=yes,scrollbars=yes');
win.focus();
return false;
}
function closePopup(win, newID, newRepr, id) {
console.log(id)
$(id + "_to").append('<option value=' + newID + ' title=' + newRepr + ' >' + newRepr + '</option>')
win.close();
}
document.addEventListener('drop', function (e) { e.preventDefault(); }, false);
function add_input_form() {
var form_idx = $('#id_answers-TOTAL_FORMS').val();
$('#form_set').append($('#empty_form').html().replace(/__prefix__/g, form_idx));
$('#id_answers-TOTAL_FORMS').val(parseInt(form_idx) + 1);
}
$(document).ready(function () {
$("#add_abnormality").appendTo($("label[for='id_abnormality']"));
$("#add_examination").appendTo($("label[for='id_examination']"));
$("#add_region").appendTo($("label[for='id_region']"));
dropContainer = document.getElementById("drop-container");
dropContainer.ondragover = function (evt) {
evt.preventDefault();
evt.stopPropagation();
};
dropContainer.ondragenter = function (evt) {
$(evt.target).addClass("drop-target-active")
evt.preventDefault();
evt.stopPropagation();
};
dropContainer.ondragleave = function (evt) {
$(evt.target).removeClass("drop-target-active")
evt.preventDefault();
evt.stopPropagation();
};
dropContainer.ondrop = function (evt) {
console.log("drop event, ", evt);
$(evt.target).removeClass("drop-target-active");
// Get all input elements
input = document.getElementById("id_image");
//fileInput = document.getElementById("id_images-0-image");
f = evt.dataTransfer.files[0]
let dT = new DataTransfer();
dT.clearData();
dT.items.add(f);
input.files = dT.files;
ocr(input);
// Make sure we have enough input targets
//input_diff = (evt.dataTransfer.files.length - inputs.length)
//console.log("diff", input_diff)
//if (input_diff > 0) {
// for (let j = 0; j < input_diff; j++) {
// add_input_form()
// }
// // Need to make sure we include the new ones...
// inputs = $("#rapid-form input[type=file][id^=id_images-]");
//}
// Loop through each dropped file and try to assign to an
// input element
//[...evt.dataTransfer.files].forEach((f) => {
// let dT = new DataTransfer();
// dT.clearData();
// dT.items.add(f);
// for (let i = 0; i < inputs.length; i++) {
// el = inputs.get(i)
// if (el.files.length == 0) {
// el.files = dT.files;
// ocr(el)
// if (evt.target.id == "feedback-drop-target") {
// arr = el.name.split("-");
// arr.splice(2, 1, "feedback_image");
// feedback_el_name = arr.join("-");
// $(`[name=${feedback_el_name}]`).prop("checked", true);
// }
// return false
// }
// }
//})
// // If you want to use some of the dropped files
// const dT = new DataTransfer();
// dT.items.add(evt.dataTransfer.files[0]);
// dT.items.add(evt.dataTransfer.files[3]);
// fileInput.files = dT.files;
evt.preventDefault();
evt.stopPropagation();
updateFileList();
};
$('#add_more').click(() => { add_input_form() });
function updateFileList() {
$("#drop-filenames").empty()
$("#id_image").each((n, el) => {
console.log(el);
if (el.files.length > 0) {
extra_class = " image-ident-loading";
if ($(el).hasClass("image-ident-warning")) {
extra_class = " image-ident-warning"
} else if ($(el).hasClass("image-ident-ok")) {
extra_class = " image-ident-ok"
}
$("#drop-filenames").append(
`<span>${n}: ${el.files[0].name}</span><img class='uploading${extra_class}' data-input-id='${el.id}' src=${URL.createObjectURL(el.files[0])}>`
)
}
})
}
$("input[type=file]").on('change', function () {
$(this).removeClass("image-ident-warning");
$(this).removeClass("image-ident-ok");
updateFileList();
console.log("input change1");
console.log("input change", this);
ocr(this);
});
//document.getElementById("id_question_type").selectedIndex = 1;
answer_1_status = document.getElementById("id_answers-0-status").selectedIndex;
if (answer_1_status == 0) {
document.getElementById("id_answers-0-status").selectedIndex = 3;
}
});
function ocr(input) {
// Tesseract.recognize(f, "eng",{ logger: m => console.log(m) }
// ).then(({ data: { text } }) => {
// console.log(text);
// l = text.toLowerCase();
// if (l.includes("accesion") || l.includes("number") || l.search(/(REF|RK9|RH8|RA9)\d+/g)) {
// console.log("SHIT", this);
// }
// })
const worker = Tesseract.createWorker({
workerPath: '{% static "worker.min.js" %}',
langPath: '{% static "" %}',
//langPath: '{{ STATIC_URL }}',
corePath: '{% static "tesseract-core.wasm.js" %}',
});
const url = URL.createObjectURL(input.files[0]);
let img = new Image;
img.src = url;
img.onload = function () {
width = img.width;
// const rectangle = { left: 0, top: 0, width: width, height: 10 }
(async () => {
await worker.load();
await worker.loadLanguage('eng');
await worker.initialize('eng');
// const { data: { text } } = await worker.recognize(input.files[0], { rectangle });
const { data: { text } } = await worker.recognize(input.files[0]);
//console.log(text);
l = text.toLowerCase();
$(`img[data-input-id='${input.id}'`).removeClass("image-ident-loading");
console.log(l);
if (l.includes("accesion") || l.includes("number") || l.search(/(ref|rk9|rh8|ra9|rbz)\d+/g) > -1) {
console.log("SHIT", input);
$(input).addClass("image-ident-warning");
$(`img[data-input-id='${input.id}'`).addClass("image-ident-warning");
} else {
$(input).addClass("image-ident-ok");
}
await worker.terminate();
})();
}
}
</script>
{{ form.media }}
{% endblock %}
{% block content %}
<h2>Submit Question</h2>
<form action="" method="post" enctype="multipart/form-data" id="anatomyquestion-form">
{% csrf_token %}
<a href="/rapids/abnormality/create" id="add_abnormality" class="add-popup"
onclick="return showAddPopup(this);"><img src="{% static '/img/icon-addlink.svg' %}"></a>
<a href="/rapids/examination/create" id="add_examination" class="add-popup"
onclick="return showAddPopup(this);"><img src="{% static '/img/icon-addlink.svg' %}"></a>
<a href="/rapids/region/create" id="add_region" class="add-popup" onclick="return showAddPopup(this);"><img
src="{% static '/img/icon-addlink.svg' %}"></a>
<table>
{{ form.as_table }}
</table>
<div id="drop-container" class="drop-target">Drop image here
<div id="drop-filenames"></div>
</div>
<h3>Answers:</h3>
<input type="button" value="Add More Answers" id="add_more">
<div id="form_set">
{% for form in answer_formset %}
<ul class="no-error answer-formset">
{{form.non_field_errors}}
{{form.errors}}
{{ form.as_ul }}
</ul>
{% endfor %}
</div>
{{ answer_formset.management_form }}
<input type="submit" class="submit-button" value="Submit" name="submit">
<input type="submit" class="submit-button" value="Submit and Clone" name="submit-clone">
</form>
<div id="empty_form" style="display:none">
<ul class='no_error answer-formset'>
{{ answer_formset.empty_form.as_ul }}
</ul>
</div>
{% endblock %}
+2 -1
View File
@@ -16,7 +16,8 @@
<script src="{% static 'js/cornerstone/cornerstoneWADOImageLoader.js' %}"></script> <script src="{% static 'js/cornerstone/cornerstoneWADOImageLoader.js' %}"></script>
<script src="{% static 'js/cornerstone/cornerstone-base64-image-loader.umd.js' %}"></script> <script src="{% static 'js/cornerstone/cornerstone-base64-image-loader.umd.js' %}"></script>
<script src="{% static 'js/anatomy.js' %}" defer="defer"></script> <script src="{% static 'js/anatomy.js' %}" defer="defer"></script>
{% block js %}
{% endblock %}
</head> </head>
<body> <body>
+2
View File
@@ -7,6 +7,8 @@ urlpatterns = [
# path('', views.question_list, name='question_list'), # path('', views.question_list, name='question_list'),
path("", views.index, name="index"), path("", views.index, name="index"),
path("question/<int:pk>/", views.question_detail, name="question_detail"), path("question/<int:pk>/", views.question_detail, name="question_detail"),
path("question/create/", views.AnatomyQuestionCreate.as_view(), name="anatomy_question_create"),
path("question/<int:pk>/update", views.AnatomyQuestionUpdate.as_view(), name="anatomy_question_create"),
path("question/<int:pk>/save_annotation", views.question_save_annotation, name="question_save_annotation"), path("question/<int:pk>/save_annotation", views.question_save_annotation, name="question_save_annotation"),
path("question/<int:pk>/answer/", path("question/<int:pk>/answer/",
views.answer_question, views.answer_question,
+113 -2
View File
@@ -6,14 +6,22 @@ from django import forms
from django.contrib.auth.decorators import login_required, user_passes_test from django.contrib.auth.decorators import login_required, user_passes_test
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic.edit import CreateView, UpdateView, DeleteView
from django.views.generic import ListView
from django.db.models.functions import Lower from django.db.models.functions import Lower
from django.core.cache import cache from django.core.cache import cache
from django.urls import reverse_lazy, reverse
from django.http import Http404, JsonResponse from django.http import Http404, JsonResponse
from django.http import HttpResponseRedirect, HttpResponse
from .forms import AnatomyAnswerForm, MarkAnatomyQuestionForm from .forms import AnatomyAnswerForm, AnswerFormSet, MarkAnatomyQuestionForm, AnatomyQuestionForm
from .models import ( from .models import (
AnatomyQuestion, AnatomyQuestion,
CidUserAnswer, CidUserAnswer,
@@ -748,4 +756,107 @@ def cid_scores(request, pk):
"exams": exams, "exams": exams,
"cid": cid, "cid": cid,
}, },
) )
class AnatomyQuestionCreateBase(LoginRequiredMixin, CreateView):
model = AnatomyQuestion
form_class = AnatomyQuestionForm
def get_context_data(self, **kwargs):
context = super(AnatomyQuestionCreateBase, self).get_context_data(**kwargs)
if self.request.POST:
context['answer_formset'] = AnswerFormSet(self.request.POST,
self.request.FILES)
else:
context['answer_formset'] = AnswerFormSet()
return context
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.save()
form.instance.author.add(self.request.user.id)
context = self.get_context_data(form=form)
formset = context['answer_formset']
if formset.is_valid():
response = super().form_valid(form)
formset.instance = self.object
formset.save()
# If the normal submit button is pressed we save as normal
if "submit" in self.request.POST:
return response
# else we redirect to the clone url
else:
return redirect('AnatomyQuestions:AnatomyQuestion_clone', pk=self.object.pk)
else:
return super().form_invalid(form)
#@login_required
class AnatomyQuestionCreate(AnatomyQuestionCreateBase):
#initial = {'laterality': AnatomyQuestion.NONE}
def get_initial(self):
pass
# # There has to be a better way...
# try:
# s = (i.pk for i in self.request.user.AnatomyQuestion_default.site.all())
# self.initial.update({'site': s})
# except AttributeError:
# pass
# return self.initial
#
# fields = '__all__'
# #fields = [ 'condition' ]
# #initial = {'date_of_death': '05/01/2018'}
# exclude = [ 'created_date', 'published_date' ]
#self.object = form.save(commit=False)
#self.object.save()
#form.instance.author.add(self.request.user.id)
#return super().form_valid(form)
#class AnatomyQuestionUpdate(LoginRequiredMixin, AuthorOrCheckerRequiredMixin,
class AnatomyQuestionUpdate(LoginRequiredMixin, UpdateView):
model = AnatomyQuestion
form_class = AnatomyQuestionForm
# fields = '__all__'
# #fields = [ 'condition' ]
# #initial = {'date_of_death': '05/01/2018'}
# exclude = [ 'created_date', 'published_date' ]
def get_context_data(self, **kwargs):
context = super(AnatomyQuestionUpdate, self).get_context_data(**kwargs)
if self.request.POST:
context['answer_formset'] = AnswerFormSet(self.request.POST,
self.request.FILES,
instance=self.object)
context['answer_formset'].full_clean()
else:
context['answer_formset'] = AnswerFormSet(instance=self.object)
return context
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.save()
form.instance.author.add(self.request.user.id)
context = self.get_context_data(form=form)
formset = context['answer_formset']
#logger.debug(formset.is_valid())
if formset.is_valid():
response = super().form_valid(form)
formset.instance = self.object
formset.save()
return response
else:
return super().form_invalid(form)