improve modal view

This commit is contained in:
Ross
2025-05-12 23:41:25 +01:00
parent 77ee83cb08
commit ead5bfcace
3 changed files with 188 additions and 201 deletions
+187 -191
View File
@@ -1,22 +1,22 @@
{% load partials %} {% load partials %}
{% partialdef case-series %} {% partialdef case-series %}
{% for series in case.series.all %} {% for series in case.series.all %}
<span class="series-block" id="series-block-{{ series.pk }}"> <span class="series-block" id="series-block-{{ series.pk }}">
<span> <span>
<span class="series-block-series-number">Series {{ forloop.counter }}:</span><br> <span class="series-block-series-number">Series {{ forloop.counter }}:</span><br>
<a href="{% url 'atlas:series_detail' pk=series.pk %}"> <a href="{% url 'atlas:series_detail' pk=series.pk %}">
{{series.get_block}} {{series.get_block}}
</a> </a>
<br> <br>
<input type="checkbox" name="series-ids" value="{{series.pk}}"> <input type="checkbox" name="series-ids" value="{{series.pk}}">
<span class="series-block-popup-link"> <span class="series-block-popup-link">
<a href="#" <a href="#"
onclick="return window.create_popup_window('/atlas/series/{{series.pk}}', 'Series')">Popup</a> onclick="return window.create_popup_window('/atlas/series/{{series.pk}}', 'Series')">Popup</a>
</span>
</span> </span>
</span> </span>
{% endfor %} </span>
{% endfor %}
{% endpartialdef %} {% endpartialdef %}
<div class="atlas {% if case.scrapped %}atlas-scrapped{% endif %}"> <div class="atlas {% if case.scrapped %}atlas-scrapped{% endif %}">
@@ -25,86 +25,86 @@
<div class="alert alert-info" role="alert"> <div class="alert alert-info" role="alert">
This case is part of a <a href='{% url "atlas:linked_cases_overview" case.pk %}'>series.</a><br/> This case is part of a <a href='{% url "atlas:linked_cases_overview" case.pk %}'>series.</a><br/>
<div class="row"> <div class="row">
{% if case.previous_case %} {% if case.previous_case %}
<div class="col-sm-6 mb-3 mb-sm-0"> <div class="col-sm-6 mb-3 mb-sm-0">
<h5 class="card-title">Previous Case</h5> <h5 class="card-title">Previous Case</h5>
{{ case.previous_case.get_link }} {{ case.previous_case.get_link }}
</div> </div>
{% endif %} {% endif %}
{% if case.next_case %} {% if case.next_case %}
<div class="col-sm-6"> <div class="col-sm-6">
<h5 class="card-title">Next Case</h5> <h5 class="card-title">Next Case</h5>
{{ case.next_case.get_link }} {{ case.next_case.get_link }}
</div> </div>
{% endif %} {% endif %}
</div> </div>
</div> </div>
{% endif %} {% endif %}
<div> <div>
{% if case.cimar_uuid %} {% if case.cimar_uuid %}
<details><summary>Details</summary> <details><summary>Details</summary>
Case uuid: {{case.cimar_uuid}} Case uuid: {{case.cimar_uuid}}
<br/> <br/>
{% if cimar_sid %}
<a href="{{case.get_cimar_case.viewer_link}}?sid={{cimar_sid}}" target="_blank">External</a>
{% else %}
<a href="{{case.get_cimar_case.viewer_link}}" target="_blank">External</a>
{% endif %}
<button {% if cimar_sid %}
hx-get='{% url "cimar_study_viewer_embed" case.cimar_uuid %}' <a href="{{case.get_cimar_case.viewer_link}}?sid={{cimar_sid}}" target="_blank">External</a>
hx-target="#modals-here" {% else %}
hx-trigger="click" <a href="{{case.get_cimar_case.viewer_link}}" target="_blank">External</a>
data-bs-toggle="modal"
data-bs-target="#modals-here"
class="btn primary">Open Modal</button>
<div id="modals-here" {% endif %}
class="modal modal-blur fade"
style="display: none"
aria-hidden="false"
tabindex="-1">
<div class="modal-dialog modal-lg modal-dialog-centered" role="document">
<div class="modal-content"></div>
</div>
</div>
<a href='{% url "cimar_study_viewer" case.cimar_uuid %}'>Viewer</a>
<details> <button
<summary>Study details</summary> hx-get='{% url "cimar_study_viewer_embed" case.cimar_uuid %}'
{{ case.get_cimar_case.get_pretty_study_details }} hx-target="#modals-here"
hx-trigger="click"
data-bs-toggle="modal"
data-bs-target="#modals-here"
class="btn primary">Open Modal</button>
<div id="modals-here"
class="modal modal-blur fade"
style="display: none"
aria-hidden="false"
tabindex="-1">
<div class="modal-dialog modal-lg modal-dialog-centered" role="document">
<div class="modal-content"></div>
</div>
</div>
<a href='{% url "cimar_study_viewer" case.cimar_uuid %}'>Viewer</a>
<details>
<summary>Study details</summary>
{{ case.get_cimar_case.get_pretty_study_details }}
</details>
<details>
<summary>Study schema</summary>
{{ case.get_cimar_case.get_pretty_study_schema }}
</details>
<details>
<summary>Study series</summary>
<button
id='load-series'
hx-get='{% url "cimar_study_series_block" case.cimar_uuid %}'
hx-trigger='load'
hx-swap='outerHTML'>
Load Series
</button>
</details>
<a href='{% url "generic:cimar_case_refresh" case.cimar_uuid %}'>Refresh</a>
</details> </details>
<details>
<summary>Study schema</summary>
{{ case.get_cimar_case.get_pretty_study_schema }}
</details>
<details>
<summary>Study series</summary>
<button
id='load-series'
hx-get='{% url "cimar_study_series_block" case.cimar_uuid %}'
hx-trigger='load'
hx-swap='outerHTML'>
Load Series
</button>
</details>
<a href='{% url "generic:cimar_case_refresh" case.cimar_uuid %}'>Refresh</a>
</details>
{% else %} {% else %}
<a href="/ohif/viewer/dicomjson?url=https://www.penracourses.org.uk{% url 'atlas:case_dicom_json' case.pk %}">View case in OHIF</a> <a target="_blank" href="/ohif/viewer/dicomjson?url=https://www.penracourses.org.uk{% url 'atlas:case_dicom_json' case.pk %}">(new tab)</a> <a href="/ohif/viewer/dicomjson?url=https://www.penracourses.org.uk{% url 'atlas:case_dicom_json' case.pk %}">View case in OHIF</a> <a target="_blank" href="/ohif/viewer/dicomjson?url=https://www.penracourses.org.uk{% url 'atlas:case_dicom_json' case.pk %}">(new tab)</a>
{% endif %} {% endif %}
<div class="date"> <div class="date">
{{ case.created_date|date:"d/m/Y" }} {{ case.created_date|date:"d/m/Y" }}
@@ -194,7 +194,7 @@
<div> <div>
<details open> <details open>
<summary><b>Findings</b></summary> <summary><b>Findings</b></summary>
{% if can_edit %} {% if can_edit %}
<details class="help-text"> <details class="help-text">
<summary><i class="bi bi-info-circle"></i> Help</summary> <summary><i class="bi bi-info-circle"></i> Help</summary>
@@ -202,23 +202,23 @@
<p>To add a finding to a series, select the series and then click the "Add finding" button.</p> <p>To add a finding to a series, select the series and then click the "Add finding" button.</p>
</details> </details>
{% endif %} {% endif %}
{% if case.series.all %} {% if case.series.all %}
{% for series in case.series.all %} {% for series in case.series.all %}
{% for finding in series.findings.all %} {% for finding in series.findings.all %}
<div class="finding-box" id="finding-box-{{ finding.pk }}" data-series="{{ series.pk }}" <div class="finding-box" id="finding-box-{{ finding.pk }}" data-series="{{ series.pk }}"
_="on mouseenter _="on mouseenter
toggle .highlight-series toggle .highlight-series
on #series-block-{{ series.pk }} on #series-block-{{ series.pk }}
until mouseleave" until mouseleave"
> >
<span> <span>
<a href="{{series.get_absolute_url}}?show_finding={{finding.pk}}"><button class="btn btn-primary btn-sm">View</button></a> <a href="{{series.get_absolute_url}}?show_finding={{finding.pk}}"><button class="btn btn-primary btn-sm">View</button></a>
<a target="_blank" href="{{series.get_absolute_url}}?show_finding={{finding.pk}}"><button class="btn btn-primary btn-sm">Popup</button></a> <a target="_blank" href="{{series.get_absolute_url}}?show_finding={{finding.pk}}"><button class="btn btn-primary btn-sm">Popup</button></a>
<button class="btn btn-secondary btn-sm view-finding-modal" data-finding-id="{{ finding.pk }}" data-series-id="{{ series.pk }}"> <button class="btn btn-secondary btn-sm view-finding-modal" data-finding-id="{{ finding.pk }}" data-series-id="{{ series.pk }}">
View in Modal View in Modal
</button> </button>
</span> </span>
{% if finding.findings.all %} {% if finding.findings.all %}
<span> <span>
@@ -282,10 +282,10 @@
<b>Collections:</b> <b>Collections:</b>
{% if can_edit %} {% if can_edit %}
<button <button
hx-get="{% url 'atlas:case_collection_form' case.pk %}" hx-get="{% url 'atlas:case_collection_form' case.pk %}"
hx-target="#collection-form"> hx-target="#collection-form">
Add to collection</button> Add to collection</button>
{% endif %} {% endif %}
<div id="collection-form"></div> <div id="collection-form"></div>
<ul> <ul>
@@ -310,11 +310,11 @@
<p class="pre-whitespace"><b>Previous case:</b> {{ case.previous_case.get_link }}</p> <p class="pre-whitespace"><b>Previous case:</b> {{ case.previous_case.get_link }}</p>
<p class="pre-whitespace"><b>Next case:</b> <p class="pre-whitespace"><b>Next case:</b>
{% if case.next_case %} {% if case.next_case %}
{{ case.next_case.get_link }} {{ case.next_case.get_link }}
{% else %} {% else %}
<a href="{% url 'atlas:case_clone_next' case.pk %}">Clone and add next case</a> <a href="{% url 'atlas:case_clone_next' case.pk %}">Clone and add next case</a>
{% endif %} {% endif %}
</p> </p>
<div> <div>
@@ -331,132 +331,128 @@
<p><b>Case size:</b> {{ case.get_total_series_images_size | filesizeformat }}</p> <p><b>Case size:</b> {{ case.get_total_series_images_size | filesizeformat }}</p>
<div class="modal fade" id="findingModal" tabindex="-1" aria-labelledby="findingModalLabel" aria-hidden="true"> <div class="modal fade" id="findingModal" tabindex="-1" aria-labelledby="findingModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg"> <div class="modal-dialog modal-lg">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title" id="findingModalLabel">Finding Details</h5> <h5 class="modal-title" id="findingModalLabel">Finding Details</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>
<div class="modal-body" id="findingModalBody"> <div class="modal-body" id="findingModalBody">
<!-- Finding details will be loaded here dynamically --> <!-- Finding details will be loaded here dynamically -->
<p>Loading...</p> <p>Loading...</p>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button> <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div> </div>
</div> </div>
</div> </div>
</div>
<script> <script>
// From series_viewer.html // From series_viewer.html
function loadAnnotationAndViewportOnElement(annotation, viewport, dicom_element, current_image_id_index) { function loadAnnotationAndViewportOnElement(annotation, viewport, dicom_element, current_image_id_index) {
cornerstoneTools.globalImageIdSpecificToolStateManager.clear(dicom_element); cornerstoneTools.globalImageIdSpecificToolStateManager.clear(dicom_element);
cornerstone.getEnabledElement(dicom_element).viewport = viewport cornerstone.getEnabledElement(dicom_element).viewport = viewport
cornerstoneTools.globalImageIdSpecificToolStateManager.restoreToolState( cornerstoneTools.globalImageIdSpecificToolStateManager.restoreToolState(
annotation); annotation);
console.log("annotation", annotation) console.log("annotation", annotation)
console.log("viewport", viewport) console.log("viewport", viewport)
//cornerstone.getEnabledElement(dicom_element).toolStateManager.restoreToolState(annotationjson) //cornerstone.getEnabledElement(dicom_element).toolStateManager.restoreToolState(annotationjson)
cornerstone.resize(dicom_element); cornerstone.resize(dicom_element);
console.log("current_image_id_index", current_image_id_index) console.log("current_image_id_index", current_image_id_index)
//current_image_id_index = parseInt(current_image_id_index); //current_image_id_index = parseInt(current_image_id_index);
console.log(current_image_id_index, Number.isInteger(current_image_id_index)) console.log(current_image_id_index, Number.isInteger(current_image_id_index))
if (Number.isInteger(current_image_id_index)) { if (Number.isInteger(current_image_id_index)) {
console.log("loading stack index") console.log("loading stack index")
dicomViewer.loadStackIndex(current_image_id_index, dicom_element); dicomViewer.loadStackIndex(current_image_id_index, dicom_element);
} else { } else {
try { try {
console.log("current_image_id_index", current_image_id_index) console.log("current_image_id_index", current_image_id_index)
dicomViewer.loadImageById(current_image_id_index, dicom_element); dicomViewer.loadImageById(current_image_id_index, dicom_element);
} catch (e) { } catch (e) {
console.log(e) console.log(e)
console.log("loading image by id failed") console.log("loading image by id failed")
console.log("loading next annotation image") console.log("loading next annotation image")
dicomViewer.getNextAnnotationImage(dicom_element); dicomViewer.getNextAnnotationImage(dicom_element);
}
} }
} }
} document.addEventListener("DOMContentLoaded", function () {
const detailsElement = document.getElementById("series-actions");
const checkboxes = document.querySelectorAll("input[name='series-ids']");
document.addEventListener("DOMContentLoaded", function () { function toggleCheckboxes() {
const detailsElement = document.getElementById("series-actions"); const isOpen = detailsElement.hasAttribute("open");
const checkboxes = document.querySelectorAll("input[name='series-ids']"); checkboxes.forEach(checkbox => {
checkbox.style.display = isOpen ? "inline-block" : "none";
function toggleCheckboxes() { });
const isOpen = detailsElement.hasAttribute("open"); }
checkboxes.forEach(checkbox => {
checkbox.style.display = isOpen ? "inline-block" : "none";
});
}
// Initial state // Initial state
toggleCheckboxes(); toggleCheckboxes();
// Listen for toggle events on the <details> element // Listen for toggle events on the <details> element
detailsElement.addEventListener("toggle", toggleCheckboxes); detailsElement.addEventListener("toggle", toggleCheckboxes);
const modal = new bootstrap.Modal(document.getElementById("findingModal")); const modal = new bootstrap.Modal(document.getElementById("findingModal"));
const modalBody = document.getElementById("findingModalBody"); const modalBody = document.getElementById("findingModalBody");
document.querySelectorAll(".view-finding-modal").forEach(button => { document.querySelectorAll(".view-finding-modal").forEach(button => {
button.addEventListener("click", function () { button.addEventListener("click", function () {
const findingId = this.getAttribute("data-finding-id"); const findingId = this.getAttribute("data-finding-id");
const seriesId = this.getAttribute("data-series-id"); const seriesId = this.getAttribute("data-series-id");
// Show loading text // Show loading text
modalBody.innerHTML = "<p>Loading...</p>"; modalBody.innerHTML = "<p>Loading...</p>";
// Fetch the finding details (replace with your actual endpoint) // Fetch the finding details (replace with your actual endpoint)
fetch(`/atlas/series_finding/${findingId}/details/`) fetch(`/atlas/series_finding/${findingId}/details/`)
.then(response => response.text()) .then(response => response.text())
.then(html => { .then(html => {
modalBody.innerHTML = html; // Load the fetched HTML into the modal body modalBody.innerHTML = html; // Load the fetched HTML into the modal body
const dicomViewer = document.getElementById('single-dicom-viewer'); const viewer_element = document.getElementById('single-dicom-viewer');
const images = JSON.parse(dicomViewer.getAttribute('data-images')); const images = JSON.parse(viewer_element.getAttribute('data-images'));
window.loadDicomViewer(images); window.loadDicomViewer(images);
annotationjson = JSON.parse(dicomViewer.dataset.annotationjson); annotationjson = JSON.parse(viewer_element.dataset.annotationjson);
viewport = JSON.parse(dicomViewer.dataset.viewportjson); viewport = JSON.parse(viewer_element.dataset.viewportjson);
current_image_id_index = dicomViewer.dataset.currentimageid; current_image_id_index = viewer_element.dataset.currentimageid;
dicom_element = $(".cornerstone-element").get(0); dicom_element = $(".cornerstone-element").get(0);
setTimeout(function () { // We have a slight delay to allow the viewer to load
console.log("loading annotation and viewport") setTimeout(function () {
// Load the annotation and viewport on the DICOM element // Then we load the annotation and viewport
loadAnnotationAndViewportOnElement(annotationjson, viewport, dicom_element, current_image_id_index); loadAnnotationAndViewportOnElement(annotationjson, viewport, dicom_element, current_image_id_index);
}, 200); }, 200);
//loadAnnotationAndViewportOnElement(annotationjson, viewport, dicom_element, current_image_id_index);
//const annotations = JSON.parse(dicomViewer.getAttribute('data-annotations')); document.getElementById("reload-finding").addEventListener("click", function () {
console.log(images) loadAnnotationAndViewportOnElement(annotationjson, viewport, dicom_element, current_image_id_index);
});
// Initialize the DICOM viewer with the images and annotations // Initialize the DICOM viewer with the images and annotations
}) })
.catch(error => { .catch(error => {
console.error("Error loading finding details:", error); console.error("Error loading finding details:", error);
modalBody.innerHTML = "<p>Failed to load finding details.</p>"; modalBody.innerHTML = "<p>Failed to load finding details.</p>";
}); });
// Show the modal // Show the modal
modal.show(); modal.show();
});
});
}); });
});
// Clear the iframe src when the modal is closed
document.getElementById("findingModal").addEventListener("hidden.bs.modal", function () {
iframe.src = "";
});
});
</script> </script>
<style> <style>
/* Highlight style for the series-block */ /* Highlight style for the series-block */
.highlight-series { .highlight-series {
background-color: blue; background-color: blue;
transition: background-color 0.3s ease, border 0.3s ease; transition: background-color 0.3s ease, border 0.3s ease;
} }
</style> </style>
@@ -1,6 +1,5 @@
<div> <div>
<h5>Finding: {{ finding.name }}</h5>
<p><b>Description:</b> {{ finding.description }}</p> <p><b>Description:</b> {{ finding.description }}</p>
{% if finding.conditions.all %} {% if finding.conditions.all %}
<p><b>Conditions:</b></p> <p><b>Conditions:</b></p>
@@ -18,6 +17,7 @@
{% endfor %} {% endfor %}
</ul> </ul>
{% endif %} {% endif %}
<button class="btn btn-primary" id="reload-finding">Reload</button>
{% with image_url_array_and_count=finding.series.get_image_url_array_and_count %} {% with image_url_array_and_count=finding.series.get_image_url_array_and_count %}
<div id="single-dicom-viewer" class="dicom-viewer" data-images='{{ image_url_array_and_count.0 }}' data-annotations='' data-annotationjson={{finding.annotation_json}} <div id="single-dicom-viewer" class="dicom-viewer" data-images='{{ image_url_array_and_count.0 }}' data-annotations='' data-annotationjson={{finding.annotation_json}}
-9
View File
@@ -314,26 +314,17 @@
cornerstone.getEnabledElement(dicom_element).viewport = viewport cornerstone.getEnabledElement(dicom_element).viewport = viewport
cornerstoneTools.globalImageIdSpecificToolStateManager.restoreToolState( cornerstoneTools.globalImageIdSpecificToolStateManager.restoreToolState(
annotation); annotation);
console.log("annotation", annotation)
console.log("viewport", viewport)
//cornerstone.getEnabledElement(dicom_element).toolStateManager.restoreToolState(annotationjson) //cornerstone.getEnabledElement(dicom_element).toolStateManager.restoreToolState(annotationjson)
cornerstone.resize(dicom_element); cornerstone.resize(dicom_element);
console.log("current_image_id_index", current_image_id_index)
//current_image_id_index = parseInt(current_image_id_index); //current_image_id_index = parseInt(current_image_id_index);
console.log(current_image_id_index, Number.isInteger(current_image_id_index))
if (Number.isInteger(current_image_id_index)) { if (Number.isInteger(current_image_id_index)) {
console.log("loading stack index")
dicomViewer.loadStackIndex(current_image_id_index, dicom_element); dicomViewer.loadStackIndex(current_image_id_index, dicom_element);
} else { } else {
try { try {
console.log("current_image_id_index", current_image_id_index)
dicomViewer.loadImageById(current_image_id_index, dicom_element); dicomViewer.loadImageById(current_image_id_index, dicom_element);
} catch (e) { } catch (e) {
console.log(e)
console.log("loading image by id failed")
console.log("loading next annotation image")
dicomViewer.getNextAnnotationImage(dicom_element); dicomViewer.getNextAnnotationImage(dicom_element);
} }
} }