From ef478459d8552365368899836c679819db0c7b28 Mon Sep 17 00:00:00 2001 From: Ross Date: Wed, 27 Jul 2022 10:06:56 +0100 Subject: [PATCH] . --- anatomy/static/js/anatomy.js | 3 +- anatomy/static/js/upload_form_helpers.js | 373 +++++++++++++++++ anatomy/tests.py | 3 - anatomy/tests/__init__.py | 0 anatomy/tests/test_exams.py | 61 +++ atlas/forms.py | 7 +- atlas/templates/atlas/series_form.html | 251 ++++------- docker/.env.dev | 9 + docker/docker-compose.yml | 27 ++ generic/views.py | 2 +- physics/urls.py | 97 +++-- rapids/forms.py | 2 +- rapids/templates/rapids/rapid_form.html | 509 ++++------------------- rapids/tests/test_urls.py | 2 +- 14 files changed, 701 insertions(+), 645 deletions(-) create mode 100644 anatomy/static/js/upload_form_helpers.js delete mode 100644 anatomy/tests.py create mode 100644 anatomy/tests/__init__.py create mode 100644 anatomy/tests/test_exams.py create mode 100644 docker/.env.dev create mode 100644 docker/docker-compose.yml diff --git a/anatomy/static/js/anatomy.js b/anatomy/static/js/anatomy.js index ede0d47d..0493c7c2 100644 --- a/anatomy/static/js/anatomy.js +++ b/anatomy/static/js/anatomy.js @@ -579,4 +579,5 @@ function delete_multiple(url, csrf_token) { } } -window.delete_multiple = delete_multiple \ No newline at end of file +window.delete_multiple = delete_multiple + diff --git a/anatomy/static/js/upload_form_helpers.js b/anatomy/static/js/upload_form_helpers.js new file mode 100644 index 00000000..8fff2006 --- /dev/null +++ b/anatomy/static/js/upload_form_helpers.js @@ -0,0 +1,373 @@ + let active_file_inputs = new Set(); + + 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); + } + + async function blobToBase64(blob) { + return new Promise((resolve, _) => { + const reader = new FileReader(); + reader.onloadend = () => resolve(reader.result); + reader.readAsDataURL(blob); + }); + } + + function extendInputs(form_id, n) { + // Makes sure we have n inputs available + // returns available inputs + inputs = getUnusedInputs($(`#${form_id} 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($(`#${form_id} input[type=file][id^=id_images-]`)); + } + return inputs; + } + + function getCurrentFormFiles(form_id) { + inputs = $(`#${form_id} 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 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 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; + } + } + + } + + + function ocr2(url, input) { + console.log("OCR", input) + Tesseract.recognize( + url, + 'eng', + //{ logger: m => console.log(m) } + ).then(({ + data: { + text + } + }) => { + l = text.toLowerCase(); + uploading_el = $(`img[data-input-id='${input.id}'], div[data-input-id='${input.id}']`) + uploading_el.removeClass("image-ident-loading"); + console.log("found text", l); + if (l.includes("accesion") || l.includes("number") || l.search( + /(ref|rk9|rh8|ra9|rbz)\d+/g) > -1) { + console.log("Match found ", input); + $(input).addClass("image-ident-warning"); + uploading_el.addClass("image-ident-warning"); + } else { + $(input).addClass("image-ident-ok"); + + } + }) + + } + + function input_change(evt) { + file = evt.target.files[0]; + $(evt.target).removeClass("image-ident-warning"); + $(evt.target).removeClass("image-ident-ok"); + + el = evt.target; + + readFileAndProcess2(el); + + } + + 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('') + win.close(); + } + + + async function readFileAndProcess2(el) { + + //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); + + + + + $(el).parent().parent().find(".temp-thumb").remove(); + + let url = URL.createObjectURL(el.files[0]) + + n = el.id.split("-")[1]; + $(`#drop-filenames span[data-input-id='${el.id}']`).remove(); + + let 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"; + } + + //let extra_class = ""; + let imageId; + if (el.files[0].type.startsWith("image")) { + + let hash = CryptoJS.MD5(el.files[0]).toString(); + + base = await blobToBase64(el.files[0]); + imageId = "base64://" + base.split(",")[1]; + imageId2 = base; + const element = $(``).get(0) + $(el).parent().parent().prepend(element); + + + ocr2(url, el); + checkHash(hash, el); + + if ($("#drop-filenames").length > 0) { + $("#drop-filenames").append( + `${n}: ${el.files[0].name}` + ) + } + } else { + imageId = `wadouri:${url}`; + imageId2 = imageId + + const element = $(`
`).get(0) + $(el).parent().parent().prepend(element); + cornerstone.enable(element); + cornerstone.loadAndCacheImage(imageId).then(function (image) { + cornerstone.displayImage(element, image); + cornerstone.resize(element) + + + if (hash_url) { + let pixel_data = image.getPixelData().toString() + + let hash = CryptoJS.MD5(pixel_data).toString(); + checkHash(hash, el); + } + + + console.log("image", image) + + extractDicomDetails(image.data.byteArray) + + }); + + if ($("#drop-filenames").length > 0) { + $("#drop-filenames").append( + `${n}: ${el.files[0].name}
` + ) + + const element2 = $(`#drop-filenames div[data-input-id='${el.id}']`).get(0); + cornerstone.enable(element2); + cornerstone.loadAndCacheImage(imageId).then(function (image) { + cornerstone.displayImage(element2, image); + + $(element2).addClass("temp-thumb-large"); + cornerstone.resize(element2) + setTimeout(function () { + ocr2($(element2).find("canvas").get(0).toDataURL(), el); + $(element2).removeClass("temp-thumb-large"); + cornerstone.resize(element2) + }, 500); + }); + } + + } + + + + + + 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) { + //load_viewer = true; + loadViewer(); + } + } + //}) + } + + function checkHash(hash, input, url) { + $.ajax({ + url: hash_url, + data: { + csrfmiddlewaretoken: csrf_token, + hash: hash + }, + type: "POST", + dataType: "json", + }) + // $.ajax().done(), $.ajax().fail(), $ajax().always() are upto you. Add/change accordingly + .done(function (data) { + console.log(data); + + if (data.status == "success") { + console.log("success"); + console.log(data.id) + if (data.id) { + //toastr.info(`id ${data.id}`) + uploading_el = $(`img[data-input-id='${input.id}'], div[data-input-id='${input.id}']`) + $(input).addClass("image-duplicate"); + uploading_el.addClass("image-duplicate"); + + toastr.warning(`Image (${input.files[0].name}) already exists.
Click to view
`, "Duplicate image", { + "closeButton": false, + "debug": false, + "newestOnTop": false, + "progressBar": false, + "positionClass": "toast-top-full-width", + "preventDuplicates": false, + "onclick": null, + "showDuration": "0", + "hideDuration": "0", + "timeOut": 0, + "extendedTimeOut": 0, + "showEasing": "swing", + "hideEasing": "linear", + "showMethod": "fadeIn", + "hideMethod": "fadeOut", + "tapToDismiss": false + }) + + } + } + }) + .always(function () { + console.log('[Done]'); + }) + } + + + 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); + $(ul).find("input[name*='filename']").val(file_name); + } + + if (file_element.files.length > 0 || $(ul).find(":contains('Currently:')")) { + n++; + $(ul).find("input[type=number]").val(n); + } + } + + } + + + 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(); + //} + + } \ No newline at end of file diff --git a/anatomy/tests.py b/anatomy/tests.py deleted file mode 100644 index 7ce503c2..00000000 --- a/anatomy/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/anatomy/tests/__init__.py b/anatomy/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/anatomy/tests/test_exams.py b/anatomy/tests/test_exams.py new file mode 100644 index 00000000..9248da54 --- /dev/null +++ b/anatomy/tests/test_exams.py @@ -0,0 +1,61 @@ +import json +from re import A +import pytest + +# from django.contrib.auth.models import User +from django.urls import reverse + +from rich.pretty import pprint + +from bs4 import BeautifulSoup + +from anatomy.views import GenericExamViews as AnatomyExamViews + +from anatomy.models import Exam + +from generic.models import CidUser, CidUserGroup + +APP_NAME = "anatomy" + +def create_cid_user_and_groups(db): + group1 = CidUserGroup.objects.create(name="Group1") + group2= CidUserGroup.objects.create(name="Group2") + + cid_user_1000 = CidUser.objects.create(cid=1000, passcode="ABCD", group=group1) + cid_user_1001 = CidUser.objects.create(cid=1001, passcode="EFGH", group=group1) + cid_user_2001 = CidUser.objects.create(cid=2001, passcode="EFGH", group=group2) + + assert cid_user_1000.cid == 1000 + assert cid_user_1001.cid == 1001 + + + + +def create_exam(db): + exam: Exam = AnatomyExamViews.Exam.objects.create(name="Cid Exam", exam_mode=True, active=True) + + group1: CidUserGroup = CidUserGroup.objects.get(name="Group1") + + exam.cid_user_groups.add(group1) + + exam.valid_cid_users.add(*group1.ciduser_set.all()) + + assert exam in group1.anatomy_cid_user_groups.all() + + assert exam + +def test_exams(db, client): + create_cid_user_and_groups(db) + create_exam(db) + + active_exams = client.get(reverse(f"{APP_NAME}:active_exams")) + assert active_exams.status_code == 200 + assert len(json.load(active_exams.content)) == 0 + + cid_user_1001 = CidUser.objects.get(cid=1001) + + for cid_user in [cid_user_1001] + active_exams_cid = client.get(reverse(f"{APP_NAME}:active_exams_cid", )) + + print(active_exams_cid.status_code) + print(active_exams_cid.content) diff --git a/atlas/forms.py b/atlas/forms.py index ea46d480..e3a4fd0f 100755 --- a/atlas/forms.py +++ b/atlas/forms.py @@ -149,7 +149,7 @@ class SeriesForm(ModelForm): "all": ["css/widgets.css"], } # Adding this javascript is crucial - js = ["jsi18n.js", "tesseract.min.js"] + js = ["jsi18n.js", "tesseract.min.js", "js/upload_form_helpers.js"] def __init__(self, *args, **kwargs): self.user = kwargs.pop( @@ -239,7 +239,7 @@ class CaseForm(ModelForm): "all": ["css/widgets.css"], } # Adding this javascript is crucial - js = ["jsi18n.js", "tesseract.min.js"] + js = ["jsi18n.js", "tesseract.min.js", "js/upload_form_helpers.js"] def __init__(self, *args, **kwargs): logging.info("LOG") @@ -371,6 +371,7 @@ CaseDifferentialFormSet = inlineformset_factory( class CaseSeriesForm(ModelForm): + def __init__(self, *args, user, **kwargs): super(CaseSeriesForm, self).__init__(*args, **kwargs) @@ -386,6 +387,8 @@ class CaseSeriesForm(ModelForm): ) + + class CaseCollectionCaseForm(ModelForm): def __init__(self, *args, user, **kwargs): super(CaseCollectionCaseForm, self).__init__(*args, **kwargs) diff --git a/atlas/templates/atlas/series_form.html b/atlas/templates/atlas/series_form.html index 3cecd608..5e0bd6a5 100755 --- a/atlas/templates/atlas/series_form.html +++ b/atlas/templates/atlas/series_form.html @@ -6,15 +6,10 @@ diff --git a/rapids/tests/test_urls.py b/rapids/tests/test_urls.py index cde6b9f0..03884b7e 100644 --- a/rapids/tests/test_urls.py +++ b/rapids/tests/test_urls.py @@ -179,4 +179,4 @@ def test_index(client, create_superuser, exam_views, django_user_model): print(json_response) - assert len(json_response) == 2 + assert len(json_response) == 2 \ No newline at end of file