many improvements
This commit is contained in:
@@ -167,7 +167,7 @@ $(document).ready(function () {
|
|||||||
|
|
||||||
window.loadDicomViewerEvent = new Event('loadDicomViewer');
|
window.loadDicomViewerEvent = new Event('loadDicomViewer');
|
||||||
window.addEventListener('loadDicomViewer', function (e) {
|
window.addEventListener('loadDicomViewer', function (e) {
|
||||||
//console.log("listen", e.detail)
|
console.log("LoadDicomViewer event", e.detail)
|
||||||
let images = [];
|
let images = [];
|
||||||
//console.log($("#image_form_set img"));
|
//console.log($("#image_form_set img"));
|
||||||
$(e.detail).each((n, el) => {
|
$(e.detail).each((n, el) => {
|
||||||
@@ -179,6 +179,7 @@ window.addEventListener('loadDicomViewer', function (e) {
|
|||||||
|
|
||||||
window.loadDicomViewerEvent = new Event('loadDicomViewerUrls');
|
window.loadDicomViewerEvent = new Event('loadDicomViewerUrls');
|
||||||
window.addEventListener('loadDicomViewerUrls', function (e) {
|
window.addEventListener('loadDicomViewerUrls', function (e) {
|
||||||
|
console.log("LoadDicomViewer unls event", e.detail)
|
||||||
|
|
||||||
loadDicomViewer(e.detail.images, e.detail.annotations);
|
loadDicomViewer(e.detail.images, e.detail.annotations);
|
||||||
}, false);
|
}, false);
|
||||||
|
|||||||
@@ -66,6 +66,7 @@
|
|||||||
|
|
||||||
|
|
||||||
function addFile(f, feedback) {
|
function addFile(f, feedback) {
|
||||||
|
console.log("add file", f)
|
||||||
let dT = new DataTransfer();
|
let dT = new DataTransfer();
|
||||||
dT.clearData();
|
dT.clearData();
|
||||||
dT.items.add(f);
|
dT.items.add(f);
|
||||||
@@ -162,9 +163,6 @@
|
|||||||
// Probably no need to await here anymore
|
// Probably no need to await here anymore
|
||||||
//await readFile(el.files[0], el);
|
//await readFile(el.files[0], el);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
$(el).parent().parent().find(".temp-thumb").remove();
|
$(el).parent().parent().find(".temp-thumb").remove();
|
||||||
|
|
||||||
let url = URL.createObjectURL(el.files[0])
|
let url = URL.createObjectURL(el.files[0])
|
||||||
@@ -196,9 +194,9 @@
|
|||||||
checkHash(hash, el);
|
checkHash(hash, el);
|
||||||
|
|
||||||
if ($("#drop-filenames").length > 0) {
|
if ($("#drop-filenames").length > 0) {
|
||||||
$("#drop-filenames").append(
|
$("#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}></span>`
|
`<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}></span>`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
imageId = `wadouri:${url}`;
|
imageId = `wadouri:${url}`;
|
||||||
@@ -363,7 +361,7 @@
|
|||||||
//}
|
//}
|
||||||
|
|
||||||
image_set = $("#image_form_set .temp-thumb").get() //.map(function() { return $(this).attr("src")}).get();
|
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()
|
//console.log()
|
||||||
//if (n == image_set.length) {
|
//if (n == image_set.length) {
|
||||||
const event = new CustomEvent('loadDicomViewer', {
|
const event = new CustomEvent('loadDicomViewer', {
|
||||||
@@ -377,3 +375,168 @@
|
|||||||
//}
|
//}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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<br/>Accession: ${accession_number} <br/>Patient ID: ${patient_id}<br/>`, '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);
|
||||||
|
}
|
||||||
|
}
|
||||||
+14
-3
@@ -20,7 +20,7 @@ from pydicom.errors import InvalidDicomError
|
|||||||
|
|
||||||
from django.core.files.storage import FileSystemStorage
|
from django.core.files.storage import FileSystemStorage
|
||||||
from django.conf import settings
|
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
|
from django.urls import reverse
|
||||||
|
|
||||||
@@ -39,6 +39,8 @@ from helpers.images import (
|
|||||||
pretty_print_dicom,
|
pretty_print_dicom,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
import mimetypes
|
||||||
|
|
||||||
|
|
||||||
from generic.models import (
|
from generic.models import (
|
||||||
CidUser,
|
CidUser,
|
||||||
@@ -1199,11 +1201,20 @@ class Resource(models.Model, AuthorMixin):
|
|||||||
if self.url:
|
if self.url:
|
||||||
html = f"<a target='_blank' href='{self.url}'>{self.name}</a>"
|
html = f"<a target='_blank' href='{self.url}'>{self.name}</a>"
|
||||||
elif self.file:
|
elif self.file:
|
||||||
html = f"<a target='_blank' href='{self.file.url}'>{self.name}</a>"
|
filetype, _ = mimetypes.guess_type(self.file.url)
|
||||||
|
|
||||||
|
match filetype.split("/"):
|
||||||
|
case "image", _:
|
||||||
|
html = f"<img src='{self.file.url}'>"
|
||||||
|
case "video", _:
|
||||||
|
html = f"<video controls><source src='{self.file.url}'></video>"
|
||||||
|
|
||||||
|
case _:
|
||||||
|
html = f"<a target='_blank' href='{self.file.url}'>{self.name}</a>"
|
||||||
else:
|
else:
|
||||||
html = self.name
|
html = self.name
|
||||||
|
|
||||||
return format_html(html)
|
return format_html("<details class='resource-block'><summary>{}</summary>{}</details>", self.name, mark_safe(html))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -19,14 +19,16 @@
|
|||||||
|
|
||||||
|
|
||||||
{% if not completed %}
|
{% if not completed %}
|
||||||
<h5>Resources</h4>
|
{% if resources %}
|
||||||
<ul>
|
<h5>Resources</h4>
|
||||||
{% for caseresource in resources %}
|
<ul class="no-list-style">
|
||||||
<li>
|
{% for caseresource in resources %}
|
||||||
{{caseresource.resource.get_display}}
|
<li>
|
||||||
</li>
|
{{caseresource.resource.get_display}}
|
||||||
{% endfor %}
|
</li>
|
||||||
</ul>
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
@@ -108,14 +110,16 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if completed %}
|
{% if completed %}
|
||||||
<h5>Resources</h4>
|
{% if resources %}
|
||||||
<ul>
|
<h5>Resources</h4>
|
||||||
{% for caseresource in resources %}
|
<ul class="no-list-style">
|
||||||
<li>
|
{% for caseresource in resources %}
|
||||||
{{caseresource.resource.get_display}}
|
<li>
|
||||||
</li>
|
{{caseresource.resource.get_display}}
|
||||||
{% endfor %}
|
</li>
|
||||||
</ul>
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<a href="{% url 'atlas:add_self_review' cid_user_exam.id case.id %}"><button>Add self review</button></a>
|
<a href="{% url 'atlas:add_self_review' cid_user_exam.id case.id %}"><button>Add self review</button></a>
|
||||||
|
|||||||
@@ -0,0 +1,243 @@
|
|||||||
|
{% extends 'atlas/base.html' %}
|
||||||
|
|
||||||
|
{% load static%}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h2>Upload dicoms</h2>
|
||||||
|
User: {{user}}<br/>
|
||||||
|
|
||||||
|
<div role="alert" class="alert alert-warning">
|
||||||
|
Please note this form will not annonymise files. Please ensure that you have done so prior to using it.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>This form allows you to quickly upload dicom files to the server. Select the folder containing the dicom files below.</div>
|
||||||
|
|
||||||
|
<form method="post" enctype="multipart/form-data">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="file" name="dicom_files" id="filepicker" webkitdirectory multiple>
|
||||||
|
<input type="submit" value="Upload">
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div>Selected files:</div>
|
||||||
|
<div id="single-dicom-viewer" class="dicom-viewer" data-images="" data-annotations=''></div>
|
||||||
|
<ul id="listing"></ul>
|
||||||
|
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block js %}
|
||||||
|
<script src="{% static 'js/upload_form_helpers.js' %}"></script>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
function checkAnnonymisation(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();
|
||||||
|
console.log("STUDY", study_description);
|
||||||
|
console.log("acc", accession_number);
|
||||||
|
console.log("pid", patient_id);
|
||||||
|
|
||||||
|
|
||||||
|
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]) ||
|
||||||
|
patient_id.startsWith("pat"))
|
||||||
|
{
|
||||||
|
not_anon = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (not_anon) {
|
||||||
|
toastr.warning(`File does not appear to be annonymised<br/>Accession: ${accession_number} <br/>Patient ID: ${patient_id}<br/>`, '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)`);
|
||||||
|
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function loadDicomViewerTest(callback) {
|
||||||
|
console.log("load")
|
||||||
|
$("#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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const readFile = (file, el) => {
|
||||||
|
reader(file).then(reader => {
|
||||||
|
console.log("hello", file, el, image)
|
||||||
|
//if ($("#id_examination").find(":selected")[0].dataset.select2Id == "2") {
|
||||||
|
// var byteArray = new Uint8Array(reader.result);
|
||||||
|
// console.log("etrxact", reader)
|
||||||
|
// extractDicomStudyDescription(byteArray);
|
||||||
|
//}
|
||||||
|
var image = new Image();
|
||||||
|
image.height = 100;
|
||||||
|
image.title = file.name;
|
||||||
|
image.src = reader.result;
|
||||||
|
image.className = "temp-thumb";
|
||||||
|
console.log("read", el);
|
||||||
|
$(el).append(image);
|
||||||
|
//images.push(reader.result);
|
||||||
|
loadViewer(images);
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const dicomReader = (file) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const fileReader = new FileReader();
|
||||||
|
fileReader.onload = () => resolve(fileReader);
|
||||||
|
fileReader.readAsArrayBuffer(file);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const readDicomFile = (file) => {
|
||||||
|
return dicomReader(file).then(reader => {
|
||||||
|
var byteArray = new Uint8Array(reader.result);
|
||||||
|
console.log("etrxact", reader)
|
||||||
|
return checkAnnonymisation(byteArray);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
file_set = document.getElementById("filepicker").files;
|
||||||
|
|
||||||
|
|
||||||
|
let output = document.getElementById("listing");
|
||||||
|
images = [];
|
||||||
|
for (const file of file_set) {
|
||||||
|
//await readFile(file);
|
||||||
|
dicom = await readDicomFile(file);
|
||||||
|
|
||||||
|
if (dicom) {
|
||||||
|
let item = document.createElement("li");
|
||||||
|
item.textContent = file.webkitRelativePath;
|
||||||
|
output.appendChild(item);
|
||||||
|
//await readFile(file, item);
|
||||||
|
|
||||||
|
|
||||||
|
let url = URL.createObjectURL(file)
|
||||||
|
console.log(url)
|
||||||
|
imageId = `wadouri:${url}`;
|
||||||
|
images.push(imageId);
|
||||||
|
|
||||||
|
const element = $(`<div class='temp-thumb' src=${imageId}></div>`).get(0)
|
||||||
|
item.appendChild(element);
|
||||||
|
cornerstone.enable(element);
|
||||||
|
cornerstone.loadAndCacheImage(imageId).then(function (image) {
|
||||||
|
cornerstone.displayImage(element, image);
|
||||||
|
cornerstone.resize(element)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
let bytesView = image.getPixelData()
|
||||||
|
let str = new TextDecoder("utf-8").decode(bytesView, );
|
||||||
|
|
||||||
|
//extractDicomDetails(image.data.byteArray)
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(images)
|
||||||
|
|
||||||
|
|
||||||
|
const event = new CustomEvent('loadDicomViewerUrls', {
|
||||||
|
"detail": {"images": images, "annontations": undefined}
|
||||||
|
});
|
||||||
|
|
||||||
|
window.dispatchEvent(event);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
$(document).ready(function () {
|
||||||
|
document.getElementById("filepicker").addEventListener(
|
||||||
|
"change",
|
||||||
|
(event) => {
|
||||||
|
//let output = document.getElementById("listing");
|
||||||
|
//for (const file of event.target.files) {
|
||||||
|
// let item = document.createElement("li");
|
||||||
|
// item.textContent = file.webkitRelativePath;
|
||||||
|
// output.appendChild(item);
|
||||||
|
//}
|
||||||
|
|
||||||
|
loadDicomViewerTest();
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
<div class="floating-header">
|
<div class="floating-header">
|
||||||
|
<a href="{% url 'atlas:case_detail' pk=case.pk %}" title="View the Case">View</a>
|
||||||
<a href="{% url 'atlas:case_update' pk=case.pk %}" title="Edit the Case">Edit</a>
|
<a href="{% url 'atlas:case_update' pk=case.pk %}" title="Edit the Case">Edit</a>
|
||||||
<a href="{% url 'atlas:case_clone' pk=case.pk %}"
|
<a href="{% url 'atlas:case_clone' pk=case.pk %}"
|
||||||
title="Clone the Case (duplicate everything but the images)">Clone</a>
|
title="Clone the Case (duplicate everything but the images)">Clone</a>
|
||||||
|
|||||||
@@ -15,6 +15,10 @@
|
|||||||
|
|
||||||
{{resource.case}}
|
{{resource.case}}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<a href="{% url 'atlas:resource_view' resource.pk %}" target="_blank">View Resource</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
{% if resource.case_set.all %}
|
{% if resource.case_set.all %}
|
||||||
<h3>Cases</h3>
|
<h3>Cases</h3>
|
||||||
This resource is used in the following cases:
|
This resource is used in the following cases:
|
||||||
|
|||||||
@@ -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<br/>Accession: ${accession_number} <br/>Patient ID: ${patient_id}<br/>`, '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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{{ form.media }}
|
{{ form.media }}
|
||||||
|
|||||||
@@ -39,11 +39,13 @@ urlpatterns = [
|
|||||||
path("uploads", views.user_uploads, name="user_uploads"),
|
path("uploads", views.user_uploads, name="user_uploads"),
|
||||||
path("uploads/<int:user_pk>/user", views.other_user_uploads, name="other_user_uploads"),
|
path("uploads/<int:user_pk>/user", views.other_user_uploads, name="other_user_uploads"),
|
||||||
path("uploads/all", views.all_uploads, name="all_uploads"),
|
path("uploads/all", views.all_uploads, name="all_uploads"),
|
||||||
|
path("uploads/new", views.new_uploads, name="new_uploads"),
|
||||||
path("uploads/case/<int:case_id>", views.user_uploads, name="user_uploads_case"),
|
path("uploads/case/<int:case_id>", views.user_uploads, name="user_uploads_case"),
|
||||||
path("uploads/<int:user_pk>/user/case/<int:case_id>", views.other_user_uploads, name="other_user_uploads_case"),
|
path("uploads/<int:user_pk>/user/case/<int:case_id>", views.other_user_uploads, name="other_user_uploads_case"),
|
||||||
path("uploads/series_id/<str:series_instance_uid>", views.user_uploads_series, name="user_uploads_series"),
|
path("uploads/series_id/<str:series_instance_uid>", views.user_uploads_series, name="user_uploads_series"),
|
||||||
path("resource/", views.ResourceView.as_view(), name="resource_view"),
|
path("resource/", views.ResourceView.as_view(), name="resource_view"),
|
||||||
path("resource/<int:pk>/", views.resource_detail, name="resource_detail"),
|
path("resource/<int:pk>/", views.resource_detail, name="resource_detail"),
|
||||||
|
path("resource/<int:pk>/view", views.resource_view, name="resource_view"),
|
||||||
path("resource/<int:pk>/update", views.ResourceUpdate.as_view(), name="resource_update"),
|
path("resource/<int:pk>/update", views.ResourceUpdate.as_view(), name="resource_update"),
|
||||||
path("resource/<int:pk>/delete", views.ResourceDelete.as_view(), name="resource_delete"),
|
path("resource/<int:pk>/delete", views.ResourceDelete.as_view(), name="resource_delete"),
|
||||||
path(
|
path(
|
||||||
|
|||||||
@@ -370,6 +370,12 @@ def resource_detail(request, pk):
|
|||||||
|
|
||||||
return render(request, "atlas/resource_detail.html", {"resource": resource})
|
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
|
@login_required
|
||||||
def author_detail(request, pk):
|
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},
|
{"dicoms": dicoms, "series_id": series_instance_uid},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def new_uploads(request):
|
||||||
|
return render(request, "atlas/new_uploads.html", {})
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@user_passes_test(lambda u: u.is_superuser)
|
@user_passes_test(lambda u: u.is_superuser)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<br/>
|
<br/>
|
||||||
{% if can_edit %}
|
{% if can_edit %}
|
||||||
<a href="{% url exam.get_app_name|add:':exam_update' exam.id %}" title="Edit the Exam">Edit</a>
|
<a href="{% url exam.get_app_name|add:':exam_dteail' exam.id %}" title="View the exam">Edit</a>
|
||||||
|
\ <a href="{% url exam.get_app_name|add:':exam_update' exam.id %}" title="Edit the Exam">Edit</a>
|
||||||
\ <a href="{% url exam.get_app_name|add:':exam_delete' exam.id %}" title="Delete the Exam">Delete</a>
|
\ <a href="{% url exam.get_app_name|add:':exam_delete' exam.id %}" title="Delete the Exam">Delete</a>
|
||||||
\ <a href="{% url exam.get_app_name|add:':exam_clone' exam.id %}" title="Clone the Exam">Clone</a>
|
\ <a href="{% url exam.get_app_name|add:':exam_clone' exam.id %}" title="Clone the Exam">Clone</a>
|
||||||
\ <a href="{% url exam.get_app_name|add:':exam_authors' exam.id %}" title="Edit Exam Authors">Authors</a>
|
\ <a href="{% url exam.get_app_name|add:':exam_authors' exam.id %}" title="Edit Exam Authors">Authors</a>
|
||||||
|
|||||||
@@ -1110,3 +1110,16 @@ tr:has(> td > a) {
|
|||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
color: #3282b8;
|
color: #3282b8;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.resource-block {
|
||||||
|
max-width: fit-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource-block video {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-list-style {
|
||||||
|
list-style-type: none;
|
||||||
|
padding-left: 1rem;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user