Files
penracourses/rapids/templates/rapids/rapid_form.html
T
2021-11-11 18:12:20 +00:00

765 lines
27 KiB
HTML
Executable File

{% extends "rapids/base.html" %}
{% load static from static %}
{% block js %}
<!--<script type="text/javascript" src="/admin/jsi18n/"></script>-->
<script type="text/javascript">
let normal = false;
let monitor_directory_handler = false;
let monitor_interval_id = false;
// set to hold the files that are still being processed
let active_file_inputs = new Set()
async function listAllFilesAndDirs(dirHandle, files_only) {
const files = [];
for await (let [name, handle] of dirHandle) {
const {
kind
} = handle;
if (handle.kind === 'directory') {
if (files_only != true) {
files.push({
name,
handle,
kind
});
}
files.push(...await listAllFilesAndDirs(handle));
} else {
files.push({
name,
handle,
kind
});
}
}
return files;
}
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) {
$(id + "_to").append('<option value=' + newID + ' title=' + newRepr + ' >' + newRepr + '</option>')
win.close();
}
document.addEventListener('drop', function (e) {
e.preventDefault();
}, false);
function getUnusedInputs(inputs) {
new_inputs = [];
for (let i = 0; i < inputs.length; i++) {
input = inputs.get(i);
if (!input.value) {
new_inputs.push(input);
}
}
return $(new_inputs);
}
function getCurrentFiles() {
inputs = $("#rapid-form input[type=file]");
let files = new Set();
for (let j = 0; j < inputs.length; j++) {
i = inputs.get(j);
if (i.files.length > 0) {
files.add(i.files[0]);
}
}
return files;
}
function extendInputs(n) {
// Makes sure we have n inputs available
// returns available inputs
inputs = getUnusedInputs($("#rapid-form input[type=file][id^=id_images-]"));
//fileInput = document.getElementById("id_images-0-image");
// Make sure we have enough input targets
input_diff = (n - inputs.length)
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 = getUnusedInputs($("#rapid-form input[type=file][id^=id_images-]"));
}
return inputs;
}
function add_input_form() {
var form_idx = $('#id_images-TOTAL_FORMS').val();
$('#image_form_set').append($($('#empty_form').html().replace(/__prefix__/g, form_idx)).on("change",
input_change));
$('#id_images-TOTAL_FORMS').val(parseInt(form_idx) + 1);
}
function add_answers_input_form() {
var form_idx = $('#id_answers-TOTAL_FORMS').val();
$('#answer_form_set').append($('#answer_empty_form').html().replace(/__prefix__/g, form_idx));
$(`#id_answers-${form_idx}-status`).val(2);
$('#id_answers-TOTAL_FORMS').val(parseInt(form_idx) + 1);
}
function input_change(evt) {
file = evt.target.files[0];
$(evt.target).removeClass("image-ident-warning");
$(evt.target).removeClass("image-ident-ok");
ocr(evt.target);
el = evt.target;
$(el).find(".temp-thumb").remove();
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";
}
n = el.id.split("-")[1];
$(`#drop-filenames span[data-input-id='${el.id}']`).remove();
$("#drop-filenames").append(
`<span data-input-id='${el.id}'><span>${n}: ${el.files[0].name}</span><img class='uploading${extra_class}' data-input-id='${el.id}' src=${URL.createObjectURL(el.files[0])}></span>`
)
}
//readFileAndProcess();
readFileAndProcess2();
// Check if we have selected a examination type
if (!$("#id_examination_to option").length) {
// Yes we do read the file twice (here and where loading it in the viewer)
var reader = new FileReader();
reader.onload = function (file) {
var arrayBuffer = reader.result;
// Here we have the file data as an ArrayBuffer. dicomParser requires as input a
// Uint8Array so we create that here
var byteArray = new Uint8Array(arrayBuffer);
extractDicomStudyDescription(byteArray)
}
reader.readAsArrayBuffer(file);
}
}
function addFile(f, feedback) {
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 (feedback) {
arr = el.name.split("-");
arr.splice(2, 1, "feedback_image");
feedback_el_name = arr.join("-");
$(`[name=${feedback_el_name}]`).prop("checked", true);
}
$(el).change();
return false;
}
}
}
$(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) {
console.log("ENTER")
console.log(evt)
$(evt.target).addClass("drop-target-active")
evt.preventDefault();
evt.stopPropagation();
};
dropContainer.ondragleave = function (evt) {
console.log("ENTER")
console.log(evt)
$(evt.target).removeClass("drop-target-active")
evt.preventDefault();
evt.stopPropagation();
};
dropContainer.ondrop = function (evt) {
console.log("SHIT", evt);
$(evt.target).removeClass("drop-target-active");
file_drop_number = evt.dataTransfer.files.length;
if (file_drop_number < 1) {
toastr.warning(`Drop failed (no files), try again.`);
} else {
toastr.info(`Loading ${file_drop_number} dropped image(s).`);
}
// Get all input elements
inputs = extendInputs(file_drop_number);
console.log("drop inputs", inputs, evt);
// Loop through each dropped file and try to assign to an
// input element
[...evt.dataTransfer.files].forEach((f) => {
feedback = false;
if (evt.target.id == "feedback-drop-target") {
feedback = true;
}
addFile(f, feedback)
})
// // 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();
};
$('#directory-picker-button').click(() => {
window.showDirectoryPicker().then((handler) => {
monitor_directory_handler = handler;
monitor_interval_id = setInterval(function () {
listAllFilesAndDirs(monitor_directory_handler).then(async function (
items) {
files_to_add = new Set();
current_files = getCurrentFiles();
current_files_tuple = new Set()
for (let elem of current_files) {
current_files_tuple.add(
`${elem.size}, ${elem.name}, ${elem.lastModified}`
);
}
console.log(current_files_tuple)
for (var i = 0; i < items.length; i++) {
f = await items[i].handle.getFile()
console.log("f", f)
if (!current_files_tuple.has(
`${f.size}, ${f.name}, ${f.lastModified}`
)) {
files_to_add.add(f);
}
}
// file objects differ enough that the following does not work
//let distinct_set = new Set([...files_to_add].filter(
// x => !current_files.has(x)));
extendInputs(files_to_add.size);
files_to_add.forEach((file) => {
addFile(file, false);
});
});
}, 5000);
$("#directory-picker-display").text(monitor_directory_handler.name)
.after(
$(
" <span>[clear]</span>").click((evt) => {
$(evt.target).remove();
$("#directory-picker-display").text("");
monitor_directory_handler = false;
clearInterval(monitor_interval_id);
//const files = await listAllFilesAndDirs(directoryHandle);
}));
});
});
$('#add_more_images').click(() => {
add_input_form()
});
$('#add_more_answers').click(() => {
add_answers_input_form()
});
$("input[type=file]").on('change', input_change);
function ifNormal() {
if (document.getElementById("id_normal").checked) {
$("#answer_form_set textarea").attr("required", false)
$('#answer_empty_form textarea').attr("required", false)
normal = true;
} else {
$("#answer_form_set textarea").attr("required", true)
$('#answer_empty_form textarea').attr("required", true)
normal = false;
}
}
$("#id_normal").change(() => ifNormal());
ifNormal();
$("#rapid-form").on("submit", (evt) => {
submit = true;
if (!normal) {
if ($('#id_answers-TOTAL_FORMS').val() == "0") {
submit = false;
add_answers_input_form();
toastr.error(`Please enter an answers (or select Normal)`);
evt.preventDefault();
}
}
if (!$("#id_examination_to option").length) {
submit = false;
toastr.error("Please add examination type")
evt.preventDefault();
}
if (submit) {
toastr.info(`Submiting question... please wait`, '', {
"closeButton": false,
"debug": false,
"newestOnTop": false,
"progressBar": true,
"positionClass": "toast-top-full-width",
"preventDuplicates": false,
"onclick": null,
"showDuration": "300",
"hideDuration": "1000",
"timeOut": "5000",
"extendedTimeOut": "1000",
"showEasing": "swing",
"hideEasing": "linear",
"showMethod": "fadeIn",
"hideMethod": "fadeOut"
});
}
})
});
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);
// }
// })
console.log('{% static "worker.min.js" %}');
console.log('{{ STATIC_URL }}/tesseract-core.wasm.js %}');
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();
})();
}
}
async function readFileAndProcess2(callback) {
//$("#single-dicom-viewer").empty()
//images = []
//Function that returns a promise to read the file
const reader = (file) => {
return new Promise((resolve, reject) => {
const fileReader = new FileReader();
fileReader.onload = () => resolve(fileReader);
fileReader.readAsDataURL(file);
});
}
// Add file to the process list
active_file_inputs.add(el)
const readFile = (file, el) => {
reader(file).then(reader => {
console.log("12345", reader)
$(el).parent().parent().find(".temp-thumb").remove();
if (reader.result.startsWith("data:application/octet-stream;base64")) {
const element = $(`<div class='temp-thumb' src=${reader.result}></div>`).get(0)
$(el).parent().parent().prepend(element);
const imageId = cornerstoneWADOImageLoader.wadouri.fileManager.add(
file
);
cornerstone.enable(element);
cornerstone.loadAndCacheImage(imageId).then(function (image) {
cornerstone.displayImage(element, image);
cornerstone.resize(element)
});
} else {
var image = new Image();
image.title = file.name;
image.src = reader.result;
image.className = "temp-thumb";
$(el).parent().parent().prepend(image);
//images.push(reader.result);
}
active_file_inputs.delete(el)
console.log("active", active_file_inputs)
// Only load once all queued files have been processed
if (active_file_inputs.size < 1) {
loadViewer();
}
});
}
file_set = $("#image_form_set input[type=file]");
file_set.each(async (n, el) => {
if (el.files.length > 0) {
filename = el.files[0].name;
// Probably no need to await here anymore
//await readFile(el.files[0], el);
let url = URL.createObjectURL(el.files[0])
$(el).parent().parent().find(".temp-thumb").remove();
const imageId = `wadouri:${url}`;
const element = $(`<div class='temp-thumb' src=${imageId}></div>`).get(0)
$(el).parent().parent().prepend(element);
cornerstone.enable(element);
cornerstone.loadAndCacheImage(imageId).then(function (image) {
cornerstone.displayImage(element, image);
cornerstone.resize(element)
});
active_file_inputs.delete(el)
console.log("active", active_file_inputs)
// Only load once all queued files have been processed
if (active_file_inputs.size < 1) {
loadViewer();
}
}
})
}
async function readFileAndProcess(callback) {
//$("#single-dicom-viewer").empty()
//images = []
//Function that returns a promise to read the file
const reader = (file) => {
return new Promise((resolve, reject) => {
const fileReader = new FileReader();
fileReader.onload = () => resolve(fileReader);
fileReader.readAsDataURL(file);
});
}
// Add file to the process list
active_file_inputs.add(el)
const readFile = (file, el) => {
reader(file).then(reader => {
console.log("12345", reader)
$(el).parent().parent().find(".temp-thumb").remove();
if (reader.result.startsWith("data:application/octet-stream;base64")) {
const element = $(`<div class='temp-thumb' src=${reader.result}></div>`).get(0)
$(el).parent().parent().prepend(element);
const imageId = cornerstoneWADOImageLoader.wadouri.fileManager.add(
file
);
cornerstone.enable(element);
cornerstone.loadAndCacheImage(imageId).then(function (image) {
cornerstone.displayImage(element, image);
cornerstone.resize(element)
});
} else {
var image = new Image();
image.title = file.name;
image.src = reader.result;
image.className = "temp-thumb";
$(el).parent().parent().prepend(image);
//images.push(reader.result);
}
active_file_inputs.delete(el)
console.log("active", active_file_inputs)
// Only load once all queued files have been processed
if (active_file_inputs.size < 1) {
loadViewer();
}
});
}
file_set = $("#image_form_set input[type=file]");
file_set.each(async (n, el) => {
if (el.files.length > 0) {
filename = el.files[0].name;
// Probably no need to await here anymore
await readFile(el.files[0], el);
}
})
}
function loadViewer() {
console.log("LOAD VIEWER")
// temp fix before starting viewer collapsed
$("#single-dicom-viewer").show()
//file_set = $("#image_form_set input[type=file]");
//n = 0;
//for (let i = 0; i < file_set.length; i++) {
// el = file_set.get(i)
// if (el.files.length > 0) {
// n++;
// }
//}
image_set = $("#image_form_set .temp-thumb").get()//.map(function() { return $(this).attr("src")}).get();
console.log(image_set);
//console.log()
//if (n == image_set.length) {
const event = new CustomEvent('loadDicomViewer', {
"detail": image_set
});
window.dispatchEvent(event);
sortable('.sortable');
//sortable('.sortable')[0].addEventListener('sortstop', function(e) {
updateImagePositions();
//}
}
function updateImagePositions() {
console.log("UP pos");
file_set = $("#image_form_set ul");
n = 0;
for (let i = 0; i < file_set.length; i++) {
ul = file_set.get(i);
console.log(ul)
file_element = $(ul).find("input[type=file]").get(0);
// If a (new) file is being uploaded we save it's name prior to it being hashed
if (file_element.files.length > 0) {
file_name = file_element.files[0].name
$(ul).find("input[type=text]").val(file_name);
}
if (file_element.files.length > 0 || $(ul).find(":contains('Currently:')")) {
n++;
$(ul).find("input[type=number]").val(n);
}
}
}
function extractDicomStudyDescription(byteArray) {
// We need to setup a try/catch block because parseDicom will throw an exception
// if you attempt to parse a non dicom part 10 file (or one that is corrupted)
try {
// parse byteArray into a DataSet object using the parseDicom library
var dataSet = dicomParser.parseDicom(byteArray);
// dataSet contains the parsed elements. Each element is available via a property
// in the dataSet.elements object. The property name is based on the elements group
// and element in the following format: xggggeeee where gggg is the group number
// and eeee is the element number with lowercase hex characters.
// To access the data for an element, we need to know its type and its tag.
// We will get the sopInstanceUid from the file which is a string and
// has the tag (0020,000D)
var study_description = dataSet.string('x00081030').toLowerCase();
console.log("STUDY", study_description);
// try to match with a study description (if not already selected)
if ($(`#id_examination_to option`).length < 1) {
option = $(`#id_examination_from option[title*='${study_description}' i]`);
if (option.length) {
option.appendTo($("#id_examination_to"));
toastr.success(`Examination set to ${option.attr('title')} from dicom data`);
} else {
toastr.warning(
`Unable to set examination ${study_description} from dicom data (it needs creating)`);
}
}
} catch (err) {
console.log(err);
}
}
</script>
{{ form.media }}
{% endblock %}
{% block content %}
<h2>Submit Rapid</h2>
<a href="{% url 'rapids:rapid_create_defaults' %}">Edit defaults</a>
<p>
<h3>Instructions</h3>
Abnormality, Region and Laterality are used to categorise within the system (they are not used for marking). String
answers that match those added below will be used for automatic marking. Multiple images can be added to a question.
If
the feedback image box is checked they will not be displayed when taking the question.
</p>
<div>
To monitor a directory select it here.<br />
<button id="directory-picker-button">Pick a directory</button>
<span id="directory-picker-display">No directory monitored</span>
</div>
<form action="" method="post" enctype="multipart/form-data" id="rapid-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>
<h3>Answers:</h3>
<input type="button" value="Add More Answers" id="add_more_answers">
<div id="answer_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 }}
<h3>Images:</h3>
<div id="drop-container" class="drop-target">Drop images here (or use the buttons below)<div
id="feedback-drop-target">Feedback image?<br />drop those here</div>
<div id="drop-filenames"></div>
</div>
<input type="button" value="Add More Images" id="add_more_images">
<div id="image_form_set">
{% for form in image_formset %}
<ul class="no-error image-formset">
{{form.non_field_errors}}
{{form.errors}}
{{ form.as_ul }}
</ul>
{% endfor %}
</div>
{{ image_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 image-formset'>
{{ image_formset.empty_form.as_ul }}
</ul>
</div>
<div id="answer_empty_form" style="display:none">
<ul class='no_error answer-formset'>
{{ answer_formset.empty_form.as_ul }}
</ul>
</div>
<div id="single-dicom-viewer" class="rapid-dicom-viewer" data-images="" data-annotations='' style="display:none"></div>
{% endblock %}