allow viewing of findings in case context

This commit is contained in:
Ross
2025-05-12 23:25:41 +01:00
parent 9f5fc09e23
commit 77ee83cb08
4 changed files with 139 additions and 0 deletions
@@ -214,6 +214,11 @@
>
<span>
<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>
<button class="btn btn-secondary btn-sm view-finding-modal" data-finding-id="{{ finding.pk }}" data-series-id="{{ series.pk }}">
View in Modal
</button>
</span>
{% if finding.findings.all %}
<span>
@@ -326,8 +331,57 @@
<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-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="findingModalLabel">Finding Details</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body" id="findingModalBody">
<!-- Finding details will be loaded here dynamically -->
<p>Loading...</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<script>
// From series_viewer.html
function loadAnnotationAndViewportOnElement(annotation, viewport, dicom_element, current_image_id_index) {
cornerstoneTools.globalImageIdSpecificToolStateManager.clear(dicom_element);
cornerstone.getEnabledElement(dicom_element).viewport = viewport
cornerstoneTools.globalImageIdSpecificToolStateManager.restoreToolState(
annotation);
console.log("annotation", annotation)
console.log("viewport", viewport)
//cornerstone.getEnabledElement(dicom_element).toolStateManager.restoreToolState(annotationjson)
cornerstone.resize(dicom_element);
console.log("current_image_id_index", 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)) {
console.log("loading stack index")
dicomViewer.loadStackIndex(current_image_id_index, dicom_element);
} else {
try {
console.log("current_image_id_index", current_image_id_index)
dicomViewer.loadImageById(current_image_id_index, dicom_element);
} catch (e) {
console.log(e)
console.log("loading image by id failed")
console.log("loading next annotation image")
dicomViewer.getNextAnnotationImage(dicom_element);
}
}
}
document.addEventListener("DOMContentLoaded", function () {
const detailsElement = document.getElementById("series-actions");
const checkboxes = document.querySelectorAll("input[name='series-ids']");
@@ -344,7 +398,59 @@
// Listen for toggle events on the <details> element
detailsElement.addEventListener("toggle", toggleCheckboxes);
const modal = new bootstrap.Modal(document.getElementById("findingModal"));
const modalBody = document.getElementById("findingModalBody");
document.querySelectorAll(".view-finding-modal").forEach(button => {
button.addEventListener("click", function () {
const findingId = this.getAttribute("data-finding-id");
const seriesId = this.getAttribute("data-series-id");
// Show loading text
modalBody.innerHTML = "<p>Loading...</p>";
// Fetch the finding details (replace with your actual endpoint)
fetch(`/atlas/series_finding/${findingId}/details/`)
.then(response => response.text())
.then(html => {
modalBody.innerHTML = html; // Load the fetched HTML into the modal body
const dicomViewer = document.getElementById('single-dicom-viewer');
const images = JSON.parse(dicomViewer.getAttribute('data-images'));
window.loadDicomViewer(images);
annotationjson = JSON.parse(dicomViewer.dataset.annotationjson);
viewport = JSON.parse(dicomViewer.dataset.viewportjson);
current_image_id_index = dicomViewer.dataset.currentimageid;
dicom_element = $(".cornerstone-element").get(0);
setTimeout(function () {
console.log("loading annotation and viewport")
// Load the annotation and viewport on the DICOM element
loadAnnotationAndViewportOnElement(annotationjson, viewport, dicom_element, current_image_id_index);
}, 200);
//loadAnnotationAndViewportOnElement(annotationjson, viewport, dicom_element, current_image_id_index);
//const annotations = JSON.parse(dicomViewer.getAttribute('data-annotations'));
console.log(images)
// Initialize the DICOM viewer with the images and annotations
})
.catch(error => {
console.error("Error loading finding details:", error);
modalBody.innerHTML = "<p>Failed to load finding details.</p>";
});
// Show the modal
modal.show();
});
});
// Clear the iframe src when the modal is closed
document.getElementById("findingModal").addEventListener("hidden.bs.modal", function () {
iframe.src = "";
});
});
</script>
<style>
@@ -0,0 +1,27 @@
<div>
<h5>Finding: {{ finding.name }}</h5>
<p><b>Description:</b> {{ finding.description }}</p>
{% if finding.conditions.all %}
<p><b>Conditions:</b></p>
<ul>
{% for condition in finding.conditions.all %}
<li>{{ condition.get_link }}</li>
{% endfor %}
</ul>
{% endif %}
{% if finding.structures.all %}
<p><b>Structures:</b></p>
<ul>
{% for structure in finding.structures.all %}
<li>{{ structure.get_link }}</li>
{% endfor %}
</ul>
{% endif %}
{% 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}}
data-viewportjson={{finding.viewport_json}} data-findingid={{finding.id}} data-currentimageid={{finding.current_image_id_index}}>
</div>
{% endwith %}
</div>
+1
View File
@@ -297,6 +297,7 @@ urlpatterns = [
),
path("condition/create", views.ConditionCreate.as_view(), name="condition_create"),
path("finding/", views.FindingView.as_view(), name="finding_view"),
path('series_finding/<int:finding_id>/details/', views.series_finding_details, name='series_finding_details'),
path("finding/<int:pk>", views.finding_detail, name="finding_detail"),
path(
"finding/<int:pk>/delete", views.FindingDelete.as_view(), name="finding_delete"
+5
View File
@@ -3135,3 +3135,8 @@ def remove_series_from_case(request, series_pk, case_pk):
return HttpResponse(f"Removed")
return HttpResponse("Invalid request.")
@login_required
def series_finding_details(request, finding_id):
finding = get_object_or_404(SeriesFinding, pk=finding_id)
return render(request, 'atlas/series_finding_details.html', {'finding': finding})