From f1642c7f0148d6ca609a950d166557bcf8d7ebce Mon Sep 17 00:00:00 2001 From: Ross Date: Mon, 15 Apr 2024 14:02:01 +0100 Subject: [PATCH] many improvements --- anatomy/static/js/anatomy.js | 3 +- anatomy/static/js/upload_form_helpers.js | 179 ++++++++++++- atlas/models.py | 17 +- .../atlas/collection_case_view_take.html | 36 +-- atlas/templates/atlas/new_uploads.html | 243 ++++++++++++++++++ .../templates/atlas/question_link_header.html | 1 + atlas/templates/atlas/resource_detail.html | 4 + atlas/templates/atlas/series_form.html | 159 ------------ atlas/urls.py | 2 + atlas/views.py | 9 + .../templates/generic/exam_link_headers.html | 3 +- rad/static/css/anatomy.css | 13 + 12 files changed, 481 insertions(+), 188 deletions(-) create mode 100644 atlas/templates/atlas/new_uploads.html diff --git a/anatomy/static/js/anatomy.js b/anatomy/static/js/anatomy.js index febc771d..6a044b79 100644 --- a/anatomy/static/js/anatomy.js +++ b/anatomy/static/js/anatomy.js @@ -167,7 +167,7 @@ $(document).ready(function () { window.loadDicomViewerEvent = new Event('loadDicomViewer'); window.addEventListener('loadDicomViewer', function (e) { - //console.log("listen", e.detail) + console.log("LoadDicomViewer event", e.detail) let images = []; //console.log($("#image_form_set img")); $(e.detail).each((n, el) => { @@ -179,6 +179,7 @@ window.addEventListener('loadDicomViewer', function (e) { window.loadDicomViewerEvent = new Event('loadDicomViewerUrls'); window.addEventListener('loadDicomViewerUrls', function (e) { + console.log("LoadDicomViewer unls event", e.detail) loadDicomViewer(e.detail.images, e.detail.annotations); }, false); diff --git a/anatomy/static/js/upload_form_helpers.js b/anatomy/static/js/upload_form_helpers.js index fe00eb11..f5c04474 100644 --- a/anatomy/static/js/upload_form_helpers.js +++ b/anatomy/static/js/upload_form_helpers.js @@ -66,6 +66,7 @@ function addFile(f, feedback) { + console.log("add file", f) let dT = new DataTransfer(); dT.clearData(); dT.items.add(f); @@ -162,9 +163,6 @@ // 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]) @@ -196,9 +194,9 @@ checkHash(hash, el); if ($("#drop-filenames").length > 0) { - $("#drop-filenames").append( - `${n}: ${el.files[0].name}` - ) + $("#drop-filenames").append( + `${n}: ${el.files[0].name}` + ) } } else { imageId = `wadouri:${url}`; @@ -223,7 +221,7 @@ console.log("image", image) let bytesView = image.getPixelData() - let str = new TextDecoder("utf-8").decode(bytesView, ); + let str = new TextDecoder("utf-8").decode(bytesView, ); console.log("bytes", bytesView) console.log("str", str) @@ -363,7 +361,7 @@ //} image_set = $("#image_form_set .temp-thumb").get() //.map(function() { return $(this).attr("src")}).get(); - console.log(image_set); + console.log("image set", image_set); //console.log() //if (n == image_set.length) { const event = new CustomEvent('loadDicomViewer', { @@ -376,4 +374,169 @@ updateImagePositions(); //} + } + + function extractDicomDetails(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 { + console.log("bytearray", byteArray) + // parse byteArray into a DataSet object using the parseDicom library + var dataSet = dicomParser.parseDicom(byteArray); + console.log("ds", dataSet) + + // 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'); + var accession_number = dataSet.string('x00080050').toLowerCase(); + var patient_id = dataSet.string('x00100020').toLowerCase(); + var modality = dataSet.string('x00080060'); + var projection = dataSet.string('x00185101'); + + try { + var plane = dataSet.string('x00180022').toLowerCase(); + } catch (err) { + var plane = ""; + } + console.log("STUDY", study_description); + console.log("acc", accession_number); + console.log("pid", patient_id); + console.log("projection", projection); + console.log("plane", plane); + + function select_plane(option) { + let plane_select_object = document.getElementsByName("plane")[0] + const $options = Array.from(plane_select_object.options); + const optionToSelect = $options.find(item => item.text === option); + optionToSelect.selected = true; + + } + + if (plane.includes("axial")) { + select_plane("axial"); + } else if (plane.includes("sagittal")) { + select_plane("sagittal"); + } else if (plane.includes("coronal")) { + select_plane("coronal"); + } else if (plane.includes("ap")) { + select_plane("AP"); + } else if (plane.includes("lat") | plane.includes("lateral")) { + select_plane("lateral"); + } else if (plane.includes("pa")) { + select_plane("PA"); + + } + + + modality_map = { + "DX": "Radiograph", + "CR": "Radiograph", + "CT": "CT", + "MR": "MR", + "RF": "Fluroscopy", + "US": "Ultrasound", + } + + if (modality in modality_map) { + let modality_select_object = document.getElementsByName("modality")[0] + const $options = Array.from(modality_select_object.options); + const optionToSelect = $options.find(item => item.text === modality_map[modality]); + optionToSelect.selected = true; + + + + } else { + toastr.info(`Unable to automatically set modality: ${modality}`); + } + + // If the dicom study_description contains multiple values select the first + study_description = study_description.split(",")[0].trim(); + + to_replace = ["lt", "rt", "contrast", "LT", "RT", "Contrast"]; + + for (let i = 0; i < to_replace.length; i++) { + + study_description = study_description.replace(to_replace[i], ""); + } + + let select_object = document.getElementsByName("examination")[0] + + if (select_object.value == "") { + $.get(`${select_object.dataset.autocompleteLightUrl}?q=${study_description}`, (data) => { + console.log(data) + if (data.results.length == 1) { + + $(select_object).select2("trigger", "select", { + data: data.results[0] + }) + + } + }) + + } + + not_anon = false; + + site_codes = ["ref", "rk9", "ra9", "rh8", "rbz", "rba"] + + for (let i = 0; i < site_codes.length; i++) { + if ( + accession_number.startsWith(site_codes[i]) || + patient_id.startsWith(site_codes[i])) { + not_anon = true; + } + } + + if (not_anon) { + toastr.warning(`File does not appear to be annonymised
Accession: ${accession_number}
Patient ID: ${patient_id}
`, 'Anonymisation warning', { + "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", + }); + } + + // TODO: fix + //$.ajax({ + // url: `{% url 'generic:examination-autocomplete' %}/${encodeURI(study_description)}`, + // //context: document.body, + // success: function(data){ + // console.log(data) + // } + //}); + + //// 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); + } } \ No newline at end of file diff --git a/atlas/models.py b/atlas/models.py index 21abc328..dc156059 100644 --- a/atlas/models.py +++ b/atlas/models.py @@ -20,7 +20,7 @@ from pydicom.errors import InvalidDicomError from django.core.files.storage import FileSystemStorage from django.conf import settings -from django.utils.html import format_html +from django.utils.html import format_html, mark_safe from django.urls import reverse @@ -39,6 +39,8 @@ from helpers.images import ( pretty_print_dicom, ) +import mimetypes + from generic.models import ( CidUser, @@ -1199,11 +1201,20 @@ class Resource(models.Model, AuthorMixin): if self.url: html = f"{self.name}" elif self.file: - html = f"{self.name}" + filetype, _ = mimetypes.guess_type(self.file.url) + + match filetype.split("/"): + case "image", _: + html = f"" + case "video", _: + html = f"" + + case _: + html = f"{self.name}" else: html = self.name - return format_html(html) + return format_html("
{}{}
", self.name, mark_safe(html)) diff --git a/atlas/templates/atlas/collection_case_view_take.html b/atlas/templates/atlas/collection_case_view_take.html index 9159d8f9..e715cc0a 100644 --- a/atlas/templates/atlas/collection_case_view_take.html +++ b/atlas/templates/atlas/collection_case_view_take.html @@ -19,14 +19,16 @@ {% if not completed %} -
Resources
- + {% if resources %} +
Resources
+ + {% endif %} {% endif %} @@ -108,14 +110,16 @@ {% endif %} {% if completed %} -
Resources
- + {% if resources %} +
Resources
+ + {% endif %}

diff --git a/atlas/templates/atlas/new_uploads.html b/atlas/templates/atlas/new_uploads.html new file mode 100644 index 00000000..75988bbd --- /dev/null +++ b/atlas/templates/atlas/new_uploads.html @@ -0,0 +1,243 @@ +{% extends 'atlas/base.html' %} + +{% load static%} + +{% block content %} +

Upload dicoms

+ User: {{user}}
+ + + +
This form allows you to quickly upload dicom files to the server. Select the folder containing the dicom files below.
+ +
+ {% csrf_token %} + + + +
+ +
Selected files:
+
+ + + +{% endblock %} + + +{% block js %} + + +{% endblock %} \ No newline at end of file diff --git a/atlas/templates/atlas/question_link_header.html b/atlas/templates/atlas/question_link_header.html index 22c412ce..4c1646df 100644 --- a/atlas/templates/atlas/question_link_header.html +++ b/atlas/templates/atlas/question_link_header.html @@ -1,4 +1,5 @@
+ View Edit Clone diff --git a/atlas/templates/atlas/resource_detail.html b/atlas/templates/atlas/resource_detail.html index 31cb1266..add12a2f 100755 --- a/atlas/templates/atlas/resource_detail.html +++ b/atlas/templates/atlas/resource_detail.html @@ -14,6 +14,10 @@ author: {{resource.get_authors}}
{{resource.case}} + +
+ View Resource +
{% if resource.case_set.all %}

Cases

diff --git a/atlas/templates/atlas/series_form.html b/atlas/templates/atlas/series_form.html index 468de11f..663e0cce 100755 --- a/atlas/templates/atlas/series_form.html +++ b/atlas/templates/atlas/series_form.html @@ -247,165 +247,6 @@ // // } - function extractDicomDetails(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 { - console.log("bytearray", byteArray) - // parse byteArray into a DataSet object using the parseDicom library - var dataSet = dicomParser.parseDicom(byteArray); - console.log("ds", dataSet) - - // 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'); - var accession_number = dataSet.string('x00080050').toLowerCase(); - var patient_id = dataSet.string('x00100020').toLowerCase(); - var modality = dataSet.string('x00080060'); - var projection = dataSet.string('x00185101'); - var plane = dataSet.string('x00180022').toLowerCase(); - console.log("STUDY", study_description); - console.log("acc", accession_number); - console.log("pid", patient_id); - console.log("projection", projection); - console.log("plane", plane); - - function select_plane(option) { - let plane_select_object = document.getElementsByName("plane")[0] - const $options = Array.from(plane_select_object.options); - const optionToSelect = $options.find(item => item.text === option); - optionToSelect.selected = true; - - } - - if (plane.includes("axial")) { - select_plane("axial"); - } else if (plane.includes("sagittal")) { - select_plane("sagittal"); - } else if (plane.includes("coronal")) { - select_plane("coronal"); - } else if (plane.includes("ap")) { - select_plane("AP"); - } else if (plane.includes("lat") | plane.includes("lateral")) { - select_plane("lateral"); - } else if (plane.includes("pa")) { - select_plane("PA"); - - } - - - modality_map = { - "DX": "Radiograph", - "CR": "Radiograph", - "CT": "CT", - "MR": "MR", - "RF": "Fluroscopy", - "US": "Ultrasound", - } - - if (modality in modality_map) { - let modality_select_object = document.getElementsByName("modality")[0] - const $options = Array.from(modality_select_object.options); - const optionToSelect = $options.find(item => item.text === modality_map[modality]); - optionToSelect.selected = true; - - - - } else { - toastr.info(`Unable to automatically set modality: ${modality}`); - } - - // If the dicom study_description contains multiple values select the first - study_description = study_description.split(",")[0].trim(); - - to_replace = ["lt", "rt", "contrast", "LT", "RT", "Contrast"]; - - for (let i = 0; i < to_replace.length; i++) { - - study_description = study_description.replace(to_replace[i], ""); - } - - let select_object = document.getElementsByName("examination")[0] - - if (select_object.value == "") { - $.get(`${select_object.dataset.autocompleteLightUrl}?q=${study_description}`, (data) => { - console.log(data) - if (data.results.length==1){ - - $(select_object).select2("trigger","select", { - data: data.results[0] - }) - - } - }) - - } - - not_anon = false; - - site_codes = ["ref", "rk9", "ra9", "rh8", "rbz", "rba"] - - for (let i = 0; i < site_codes.length; i++) { - if ( - accession_number.startsWith(site_codes[i]) - || patient_id.startsWith(site_codes[i])) { - not_anon = true; - } - } - - if (not_anon) { - toastr.warning(`File does not appear to be annonymised
Accession: ${accession_number}
Patient ID: ${patient_id}
`, 'Anonymisation warning', { - "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", - }); - } - - // TODO: fix - //$.ajax({ - // url: `{% url 'generic:examination-autocomplete' %}/${encodeURI(study_description)}`, - // //context: document.body, - // success: function(data){ - // console.log(data) - // } - //}); - - //// 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); - } - } {{ form.media }} diff --git a/atlas/urls.py b/atlas/urls.py index 49d3b61c..628817d3 100755 --- a/atlas/urls.py +++ b/atlas/urls.py @@ -39,11 +39,13 @@ urlpatterns = [ path("uploads", views.user_uploads, name="user_uploads"), path("uploads//user", views.other_user_uploads, name="other_user_uploads"), path("uploads/all", views.all_uploads, name="all_uploads"), + path("uploads/new", views.new_uploads, name="new_uploads"), path("uploads/case/", views.user_uploads, name="user_uploads_case"), path("uploads//user/case/", views.other_user_uploads, name="other_user_uploads_case"), path("uploads/series_id/", views.user_uploads_series, name="user_uploads_series"), path("resource/", views.ResourceView.as_view(), name="resource_view"), path("resource//", views.resource_detail, name="resource_detail"), + path("resource//view", views.resource_view, name="resource_view"), path("resource//update", views.ResourceUpdate.as_view(), name="resource_update"), path("resource//delete", views.ResourceDelete.as_view(), name="resource_delete"), path( diff --git a/atlas/views.py b/atlas/views.py index 8a286fcf..d6a76ee7 100755 --- a/atlas/views.py +++ b/atlas/views.py @@ -370,6 +370,12 @@ def resource_detail(request, pk): return render(request, "atlas/resource_detail.html", {"resource": resource}) +@login_required +def resource_view(request, pk): + resource = get_object_or_404(Resource, pk=pk) + + return HttpResponse(resource.get_display()) + @login_required def author_detail(request, pk): @@ -444,6 +450,9 @@ def user_uploads_series(request, series_instance_uid: str): {"dicoms": dicoms, "series_id": series_instance_uid}, ) +@login_required +def new_uploads(request): + return render(request, "atlas/new_uploads.html", {}) @login_required @user_passes_test(lambda u: u.is_superuser) diff --git a/generic/templates/generic/exam_link_headers.html b/generic/templates/generic/exam_link_headers.html index f973b2d8..15782766 100644 --- a/generic/templates/generic/exam_link_headers.html +++ b/generic/templates/generic/exam_link_headers.html @@ -1,6 +1,7 @@
{% if can_edit %} - Edit + Edit + \ Edit \ Delete \ Clone \ Authors diff --git a/rad/static/css/anatomy.css b/rad/static/css/anatomy.css index f74c681d..23ae4471 100644 --- a/rad/static/css/anatomy.css +++ b/rad/static/css/anatomy.css @@ -1109,4 +1109,17 @@ tr:has(> td > a) { padding: 0; text-decoration: underline; color: #3282b8; +} + +.resource-block { + max-width: fit-content; +} + +.resource-block video { + max-width: 100%; +} + +.no-list-style { + list-style-type: none; + padding-left: 1rem; } \ No newline at end of file