From 01c98cd2e1d40f93579211cf7f876ec15bb3f489 Mon Sep 17 00:00:00 2001 From: Ross Date: Fri, 20 Aug 2021 10:26:58 +0100 Subject: [PATCH] . --- anatomy/static/js/anatomy.js | 2 +- ...0030_alter_rapidimage_image_annotations.py | 18 +++ rapids/models.py | 2 +- .../rapids/question_display_block.html | 7 +- rapids/views.py | 9 +- static/debug_toolbar/css/toolbar.css | 2 +- static/debug_toolbar/js/history.js | 43 +++--- static/debug_toolbar/js/timer.js | 128 ++++++++++-------- static/debug_toolbar/js/toolbar.js | 39 ++++-- static/debug_toolbar/js/utils.js | 15 ++ static/js/anatomy.js | 2 +- static/js/dicomViewer.js | 6 +- 12 files changed, 176 insertions(+), 97 deletions(-) create mode 100644 rapids/migrations/0030_alter_rapidimage_image_annotations.py diff --git a/anatomy/static/js/anatomy.js b/anatomy/static/js/anatomy.js index b0b766d2..8bd1d64d 100644 --- a/anatomy/static/js/anatomy.js +++ b/anatomy/static/js/anatomy.js @@ -178,7 +178,7 @@ function loadDicomViewer(images_to_load, annotations_to_load) { console.log(images); let annotations = single_dicom.dataset.annotations; - if (annotations != undefined) { + if (annotations_to_load != undefined) { annotations = annotations_to_load; } else if (annotations && annotations.indexOf(",") > 0) { annotations = JSON.parse(annotations); diff --git a/rapids/migrations/0030_alter_rapidimage_image_annotations.py b/rapids/migrations/0030_alter_rapidimage_image_annotations.py new file mode 100644 index 00000000..344245a5 --- /dev/null +++ b/rapids/migrations/0030_alter_rapidimage_image_annotations.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.6 on 2021-08-20 09:09 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('rapids', '0029_rapidimage_image_annotations'), + ] + + operations = [ + migrations.AlterField( + model_name='rapidimage', + name='image_annotations', + field=models.TextField(blank=True, help_text='Stores a JSON representation of annotations to be applied by cornerstonetools', null=True), + ), + ] diff --git a/rapids/models.py b/rapids/models.py index 25d3e0d5..cf30623c 100644 --- a/rapids/models.py +++ b/rapids/models.py @@ -388,7 +388,7 @@ class RapidImage(models.Model): image = models.FileField(upload_to=image_directory_path) image_annotations = models.TextField( - blank=True, + blank=True, null=True, help_text="Stores a JSON representation of annotations to be applied by cornerstonetools", ) diff --git a/rapids/templates/rapids/question_display_block.html b/rapids/templates/rapids/question_display_block.html index 21ed2c5a..09eec9b5 100755 --- a/rapids/templates/rapids/question_display_block.html +++ b/rapids/templates/rapids/question_display_block.html @@ -89,15 +89,16 @@ Image viewer -
+
-
Image annotations: +
Image annotations: {% for image in question.images.all %} - Image {{ forloop.counter }}: {{image.image_annotations}} + Image {{ forloop.counter }}: {{image.image_annotations}}
{% endfor %}
diff --git a/rapids/views.py b/rapids/views.py index 68ad52b7..fa77810f 100755 --- a/rapids/views.py +++ b/rapids/views.py @@ -1160,10 +1160,13 @@ def question_save_annotation(request, pk): question_images = question.images.all() - for n, annotation in enumerate(image_annotations): + for n, image in enumerate(question_images): + annotation = image_annotations[n] if annotation != "undefined": - question_images[n].image_annotations = annotation - question_images[n].save() + image.image_annotations = annotation + else: + image.image_annotations = "" + image.save() data = {"status": "success"} return JsonResponse(data, status=200) diff --git a/static/debug_toolbar/css/toolbar.css b/static/debug_toolbar/css/toolbar.css index 6bbfd3d7..13fbbed0 100644 --- a/static/debug_toolbar/css/toolbar.css +++ b/static/debug_toolbar/css/toolbar.css @@ -198,7 +198,7 @@ #djDebug #djDebugToolbarHandle { position: fixed; - transform: rotate(-90deg); + transform: translateY(-100%) rotate(-90deg); transform-origin: right bottom; background-color: #fff; border: 1px solid #111; diff --git a/static/debug_toolbar/js/history.js b/static/debug_toolbar/js/history.js index e20c8543..cc14b2e4 100644 --- a/static/debug_toolbar/js/history.js +++ b/static/debug_toolbar/js/history.js @@ -7,21 +7,30 @@ $$.on(djDebug, "click", ".switchHistory", function (event) { const newStoreId = this.dataset.storeId; const tbody = this.closest("tbody"); - tbody - .querySelector(".djdt-highlighted") - .classList.remove("djdt-highlighted"); + const highlighted = tbody.querySelector(".djdt-highlighted"); + if (highlighted) { + highlighted.classList.remove("djdt-highlighted"); + } this.closest("tr").classList.add("djdt-highlighted"); ajaxForm(this).then(function (data) { djDebug.setAttribute("data-store-id", newStoreId); - Object.keys(data).forEach(function (panelId) { - const panel = document.getElementById(panelId); - if (panel) { - panel.outerHTML = data[panelId].content; - document.getElementById("djdt-" + panelId).outerHTML = - data[panelId].button; - } - }); + // Check if response is empty, it could be due to an expired store_id. + if (Object.keys(data).length === 0) { + const container = document.getElementById("djdtHistoryRequests"); + container.querySelector( + 'button[data-store-id="' + newStoreId + '"]' + ).innerHTML = "Switch [EXPIRED]"; + } else { + Object.keys(data).forEach(function (panelId) { + const panel = document.getElementById(panelId); + if (panel) { + panel.outerHTML = data[panelId].content; + document.getElementById("djdt-" + panelId).outerHTML = + data[panelId].button; + } + }); + } }); }); @@ -29,12 +38,14 @@ $$.on(djDebug, "click", ".refreshHistory", function (event) { event.preventDefault(); const container = document.getElementById("djdtHistoryRequests"); ajaxForm(this).then(function (data) { + // Remove existing rows first then re-populate with new data + container + .querySelectorAll("tr[data-store-id]") + .forEach(function (node) { + node.remove(); + }); data.requests.forEach(function (request) { - if ( - !container.querySelector('[data-store-id="' + request.id + '"]') - ) { - container.innerHTML = request.content + container.innerHTML; - } + container.innerHTML = request.content + container.innerHTML; }); }); }); diff --git a/static/debug_toolbar/js/timer.js b/static/debug_toolbar/js/timer.js index 1d4ac19d..70d3fe5a 100644 --- a/static/debug_toolbar/js/timer.js +++ b/static/debug_toolbar/js/timer.js @@ -1,59 +1,75 @@ -const timingOffset = performance.timing.navigationStart, - timingEnd = performance.timing.loadEventEnd, - totalTime = timingEnd - timingOffset; -function getLeft(stat) { - return ((performance.timing[stat] - timingOffset) / totalTime) * 100.0; -} -function getCSSWidth(stat, endStat) { - let width = - ((performance.timing[endStat] - performance.timing[stat]) / totalTime) * - 100.0; - // Calculate relative percent (same as sql panel logic) - width = (100.0 * width) / (100.0 - getLeft(stat)); - return width < 1 ? "2px" : width + "%"; -} -function addRow(tbody, stat, endStat) { - const row = document.createElement("tr"); - if (endStat) { - // Render a start through end bar - row.innerHTML = - "" + - stat.replace("Start", "") + - "" + - '' + - "" + - (performance.timing[stat] - timingOffset) + - " (+" + - (performance.timing[endStat] - performance.timing[stat]) + - ")"; - row.querySelector("rect").setAttribute( - "width", - getCSSWidth(stat, endStat) - ); - } else { - // Render a point in time - row.innerHTML = - "" + - stat + - "" + - '' + - "" + - (performance.timing[stat] - timingOffset) + - ""; - row.querySelector("rect").setAttribute("width", 2); +import { $$ } from "./utils.js"; + +function insertBrowserTiming() { + console.log(["inserted"]); + const timingOffset = performance.timing.navigationStart, + timingEnd = performance.timing.loadEventEnd, + totalTime = timingEnd - timingOffset; + function getLeft(stat) { + return ((performance.timing[stat] - timingOffset) / totalTime) * 100.0; + } + function getCSSWidth(stat, endStat) { + let width = + ((performance.timing[endStat] - performance.timing[stat]) / + totalTime) * + 100.0; + // Calculate relative percent (same as sql panel logic) + width = (100.0 * width) / (100.0 - getLeft(stat)); + return width < 1 ? "2px" : width + "%"; + } + function addRow(tbody, stat, endStat) { + const row = document.createElement("tr"); + if (endStat) { + // Render a start through end bar + row.innerHTML = + "" + + stat.replace("Start", "") + + "" + + '' + + "" + + (performance.timing[stat] - timingOffset) + + " (+" + + (performance.timing[endStat] - performance.timing[stat]) + + ")"; + row.querySelector("rect").setAttribute( + "width", + getCSSWidth(stat, endStat) + ); + } else { + // Render a point in time + row.innerHTML = + "" + + stat + + "" + + '' + + "" + + (performance.timing[stat] - timingOffset) + + ""; + row.querySelector("rect").setAttribute("width", 2); + } + row.querySelector("rect").setAttribute("x", getLeft(stat)); + tbody.appendChild(row); + } + + const browserTiming = document.getElementById("djDebugBrowserTiming"); + // Determine if the browser timing section has already been rendered. + if (browserTiming.classList.contains("djdt-hidden")) { + const tbody = document.getElementById("djDebugBrowserTimingTableBody"); + // This is a reasonably complete and ordered set of timing periods (2 params) and events (1 param) + addRow(tbody, "domainLookupStart", "domainLookupEnd"); + addRow(tbody, "connectStart", "connectEnd"); + addRow(tbody, "requestStart", "responseEnd"); // There is no requestEnd + addRow(tbody, "responseStart", "responseEnd"); + addRow(tbody, "domLoading", "domComplete"); // Spans the events below + addRow(tbody, "domInteractive"); + addRow(tbody, "domContentLoadedEventStart", "domContentLoadedEventEnd"); + addRow(tbody, "loadEventStart", "loadEventEnd"); + browserTiming.classList.remove("djdt-hidden"); } - row.querySelector("rect").setAttribute("x", getLeft(stat)); - tbody.appendChild(row); } -const tbody = document.getElementById("djDebugBrowserTimingTableBody"); -// This is a reasonably complete and ordered set of timing periods (2 params) and events (1 param) -addRow(tbody, "domainLookupStart", "domainLookupEnd"); -addRow(tbody, "connectStart", "connectEnd"); -addRow(tbody, "requestStart", "responseEnd"); // There is no requestEnd -addRow(tbody, "responseStart", "responseEnd"); -addRow(tbody, "domLoading", "domComplete"); // Spans the events below -addRow(tbody, "domInteractive"); -addRow(tbody, "domContentLoadedEventStart", "domContentLoadedEventEnd"); -addRow(tbody, "loadEventStart", "loadEventEnd"); -document.getElementById("djDebugBrowserTiming").classList.remove("djdt-hidden"); +const djDebug = document.getElementById("djDebug"); +// Insert the browser timing now since it's possible for this +// script to miss the initial panel load event. +insertBrowserTiming(); +$$.onPanelRender(djDebug, "TimerPanel", insertBrowserTiming); diff --git a/static/debug_toolbar/js/toolbar.js b/static/debug_toolbar/js/toolbar.js index d739cbdb..c17ee3ea 100644 --- a/static/debug_toolbar/js/toolbar.js +++ b/static/debug_toolbar/js/toolbar.js @@ -20,7 +20,8 @@ const djdt = { if (!this.className) { return; } - const current = document.getElementById(this.className); + const panelId = this.className; + const current = document.getElementById(panelId); if ($$.visible(current)) { djdt.hide_panels(); } else { @@ -39,13 +40,24 @@ const djdt = { window.location ); url.searchParams.append("store_id", store_id); - url.searchParams.append("panel_id", this.className); + url.searchParams.append("panel_id", panelId); ajax(url).then(function (data) { inner.previousElementSibling.remove(); // Remove AJAX loader inner.innerHTML = data.content; $$.executeScripts(data.scripts); $$.applyStyles(inner); + djDebug.dispatchEvent( + new CustomEvent("djdt.panel.render", { + detail: { panelId: panelId }, + }) + ); }); + } else { + djDebug.dispatchEvent( + new CustomEvent("djdt.panel.render", { + detail: { panelId: panelId }, + }) + ); } } } @@ -178,6 +190,7 @@ const djdt = { requestAnimationFrame(function () { djdt.handleDragged = false; }); + djdt.ensure_handle_visibility(); } }); const show = @@ -198,6 +211,15 @@ const djdt = { e.classList.remove("djdt-active"); }); }, + ensure_handle_visibility() { + const handle = document.getElementById("djDebugToolbarHandle"); + // set handle position + const handleTop = Math.min( + localStorage.getItem("djdt.top") || 0, + window.innerHeight - handle.offsetWidth + ); + handle.style.top = handleTop + "px"; + }, hide_toolbar() { djdt.hide_panels(); @@ -205,16 +227,8 @@ const djdt = { const handle = document.getElementById("djDebugToolbarHandle"); $$.show(handle); - // set handle position - let handleTop = localStorage.getItem("djdt.top"); - if (handleTop) { - handleTop = Math.min( - handleTop, - window.innerHeight - handle.offsetHeight - ); - handle.style.top = handleTop + "px"; - } - + djdt.ensure_handle_visibility(); + window.addEventListener("resize", djdt.ensure_handle_visibility); document.removeEventListener("keydown", onKeyDown); localStorage.setItem("djdt.show", "false"); @@ -237,6 +251,7 @@ const djdt = { $$.hide(document.getElementById("djDebugToolbarHandle")); $$.show(document.getElementById("djDebugToolbar")); localStorage.setItem("djdt.show", "true"); + window.removeEventListener("resize", djdt.ensure_handle_visibility); }, cookie: { get(key) { diff --git a/static/debug_toolbar/js/utils.js b/static/debug_toolbar/js/utils.js index 4683b319..da810aad 100644 --- a/static/debug_toolbar/js/utils.js +++ b/static/debug_toolbar/js/utils.js @@ -7,6 +7,21 @@ const $$ = { } }); }, + onPanelRender(root, panelId, fn) { + /* + This is a helper function to attach a handler for a `djdt.panel.render` + event of a specific panel. + + root: The container element that the listener should be attached to. + panelId: The Id of the panel. + fn: A function to execute when the event is triggered. + */ + root.addEventListener("djdt.panel.render", function (event) { + if (event.detail.panelId === panelId) { + fn.call(event); + } + }); + }, show(element) { element.classList.remove("djdt-hidden"); }, diff --git a/static/js/anatomy.js b/static/js/anatomy.js index b0b766d2..8bd1d64d 100644 --- a/static/js/anatomy.js +++ b/static/js/anatomy.js @@ -178,7 +178,7 @@ function loadDicomViewer(images_to_load, annotations_to_load) { console.log(images); let annotations = single_dicom.dataset.annotations; - if (annotations != undefined) { + if (annotations_to_load != undefined) { annotations = annotations_to_load; } else if (annotations && annotations.indexOf(",") > 0) { annotations = JSON.parse(annotations); diff --git a/static/js/dicomViewer.js b/static/js/dicomViewer.js index f964ec0f..8183c2f0 100644 --- a/static/js/dicomViewer.js +++ b/static/js/dicomViewer.js @@ -11,6 +11,7 @@ cornerstoneWADOImageLoader.external.cornerstone = cornerstone; cornerstoneTools.init(); +let tool_state = {}; export function loadCornerstone(main_element, db, images, annotations_to_load, load_as_stack = false) { @@ -180,15 +181,14 @@ export function loadCornerstone(main_element, db, images, annotations_to_load, l function loadAnnotation(imageId, annotation) { - console.log("loadAnnotations", imageId, annotations); + console.log("loadAnnotations", imageId, annotation); const toolStateManager = cornerstoneTools.globalImageIdSpecificToolStateManager; if (annotation == undefined || annotation.length < 1 || annotation == [undefined]) { return } - console.log(annotation); + console.log("1234", annotation); let tool_state_no_id = JSON.parse(annotation); - let tool_state = {}; tool_state[imageId] = tool_state_no_id; toolStateManager.restoreToolState(tool_state);