start migration to dv3d

This commit is contained in:
Ross
2025-06-09 11:39:38 +01:00
parent 503c8f854a
commit 8e92de3420
20 changed files with 4444 additions and 84 deletions
+115 -81
View File
@@ -3,28 +3,28 @@
<div>{{ series.modality}}, {{ series.examination }}, {{ series.plane }}, {{ series.contrast }}</div>
<div>Description: {{series.description}}</div>
Associated case:
{% for case in series.case.all %}
<span id="case-link-{{ case.pk }}" class="case-item">
Associated case:
{% for case in series.case.all %}
<span id="case-link-{{ case.pk }}" class="case-item">
<a href="{% url 'atlas:case_detail' pk=case.pk %}">{{case}}</a>
{% if can_edit %}
<button hx-post="{% url 'atlas:remove_series_from_case' series.pk case.pk %}"
hx-target="#case-link-{{ case.pk }}"
hx-swap="outerHTML"
hx-confirm="Are you sure you want to remove this series from the case?"
class="btn btn-link btn-sm button-unset remove-button">
Remove
</button>
{% endif %}
</span>
{% empty %}
This series is not associated with any cases.
{% if can_edit %}
<button hx-post="{% url 'atlas:remove_series_from_case' series.pk case.pk %}"
hx-target="#case-link-{{ case.pk }}"
hx-swap="outerHTML"
hx-confirm="Are you sure you want to remove this series from the case?"
class="btn btn-link btn-sm button-unset remove-button">
Remove
</button>
{% endif %}
</span>
{% empty %}
This series is not associated with any cases.
{% if can_edit %}
<details>
<summary>Add Series to Case</summary>
<form hx-post="{% url 'atlas:add_series_to_case' series.pk %}"
hx-target="#add-series-to-case-result"
hx-swap="innerHTML">
hx-target="#add-series-to-case-result"
hx-swap="innerHTML">
{% csrf_token %}
<label for="case-select">Select Case:</label>
<select name="case_id" id="case-select" required>
@@ -36,23 +36,29 @@
</form>
<div id="add-series-to-case-result"></div>
</details>
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
{% with image_url_array_and_count=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=''>
</div>
<div id="root" class="dicom-viewer-root" data-images="{{ image_url_array_and_count.0 }}"
style="width: 1000px; height: 600px; box-sizing: border-box; background: #222;"
data-auto-cache-stack=true
></div>
{% if can_edit %}
{% if editing_finding < 1 %}
<button id="add-finding-button">Add finding</button>
{% endif %}
<button id="clone-finding-button" title="Click to copy the details of a finding that is already associated with the series/case"
hx-get="{% url 'atlas:series_finding_related' series.pk %}"
hx-target="#clone-findings-modal"
hx-trigger="click"
data-bs-toggle="modal"
data-bs-target="#clone-findings-modal"
hx-get="{% url 'atlas:series_finding_related' series.pk %}"
hx-target="#clone-findings-modal"
hx-trigger="click"
data-bs-toggle="modal"
data-bs-target="#clone-findings-modal"
>Clone existing finding</button>
<button id="reset-viewport-button">Reset viewport</button>
<div id="finding-form">
@@ -80,6 +86,11 @@
<div class="finding-box">
<button id="finding-{{finding.pk}}" class="view-finding-button" data-annotationjson={{finding.annotation_json}}
data-viewportjson={{finding.viewport_json}} data-findingid={{finding.id}} data-currentimageid={{finding.current_image_id_index}}>Click to view</button>
{% if finding.viewer_state_3d %}
<button id="finding-{{finding.pk}}-3d" class="view-finding-button-3d" data-annotationjson3d='{{finding.annotation_json_3d}}' data-viewerstatejson='{{finding.viewer_state_3d}}'>Click to view 3D</button>
{% endif %}
<span class="view-finding-details">
Finding(s): {% for f in finding.findings.all %}{{f.get_link}}{% endfor %}<br />
Structure(s): {% for s in finding.structures.all %}{{s.get_link}}{% endfor %}<br />
@@ -95,6 +106,11 @@
<pre>{{finding.viewport_json}}</pre>
<h4>Image ID</h4>
<pre>{{finding.current_image_id_index}}</pre>
<h4>3D Annotation JSON</h4>
<pre>{{finding.annotation_json_3d}}</pre>
<h4>Viewer State</h4>
<pre>{{finding.viewer_state_3d}}</pre>
</div>
{% endif %}
</span>
@@ -124,76 +140,76 @@
<div>Author: {{ series.get_author_display }}</div>
{% if can_edit %}
<details>
<summary>Series info</summary>
<div>
<a href="{% url 'atlas:series_anonymise_dicom' pk=series.pk %}"
title="Anonymise dicom images">Anonymise dicoms</a><br />
<a href="{% url 'atlas:series_order_dicom' pk=series.pk %}" title="orders dicom by slice location">Order dicoms
by slice location</a>
<a href="{% url 'atlas:series_order_dicom_instance' pk=series.pk %}"
title="orders dicom by instance number">Order dicoms by instance number</a>
<a href="{% url 'atlas:series_order_dicom_SeriesInstanceUID' pk=series.pk %}"
title="orders dicom by instance number">Order dicoms by SeriesInstanceUID</a>
<a href="{% url 'atlas:series_order_upload_filename' pk=series.pk %}"
title="orders dicom by uploaded filename">Order by uploaded filename</a><br/>
<a href="{% url 'api-1:series_split_by_tag' series.pk 'ImageType' %}"
title="split series by dicom tag ImageType">Split the series by the ImageType dicom tag</a>
</div>
{% if can_edit %}
<details>
<summary>
Image details
</summary>
<form hx-post="{% url 'atlas:image_diff' %}"
hx-target="#temp"
>
{% for image in series.get_images %}
[{{ image.id }}] <a href='{% url "atlas:series_image_dicom" image.pk %}'>{{image.image.url}}</a>, pos: {{image.position}}, {{image.upload_filename}}
<summary>Series info</summary>
<div>
<a href="{% url 'atlas:series_anonymise_dicom' pk=series.pk %}"
title="Anonymise dicom images">Anonymise dicoms</a><br />
<a href="{% url 'atlas:series_order_dicom' pk=series.pk %}" title="orders dicom by slice location">Order dicoms
by slice location</a>
<a href="{% url 'atlas:series_order_dicom_instance' pk=series.pk %}"
title="orders dicom by instance number">Order dicoms by instance number</a>
<a href="{% url 'atlas:series_order_dicom_SeriesInstanceUID' pk=series.pk %}"
title="orders dicom by instance number">Order dicoms by SeriesInstanceUID</a>
<a href="{% url 'atlas:series_order_upload_filename' pk=series.pk %}"
title="orders dicom by uploaded filename">Order by uploaded filename</a><br/>
<a href="{% url 'api-1:series_split_by_tag' series.pk 'ImageType' %}"
title="split series by dicom tag ImageType">Split the series by the ImageType dicom tag</a>
</div>
<details>
<summary>
Image details
</summary>
<form hx-post="{% url 'atlas:image_diff' %}"
hx-target="#temp"
>
{% for image in series.get_images %}
[{{ image.id }}] <a href='{% url "atlas:series_image_dicom" image.pk %}'>{{image.image.url}}</a>, pos: {{image.position}}, {{image.upload_filename}}
{% if image.image %}
[{{image.get_file_size|filesizeformat}}]
{% endif %}
{% if image.image %}
[{{image.get_file_size|filesizeformat}}]
{% endif %}
{{image.image_md5_hash}} ({{image.is_dicom}}) {{image.image_blake3_hash}}
{{image.image_md5_hash}} ({{image.is_dicom}}) {{image.image_blake3_hash}}
<input type="checkbox" name="image-id" value="{{image.id}}">
<input type="checkbox" name="image-id" value="{{image.id}}">
<br />
<br />
{% comment %} {{image.get_dicom_info|safe}}<br /> {% endcomment %}
{% endfor %}
<button>Diff files</button>
</form>
<span id="temp"></span>
</details>
{% endfor %}
<button>Diff files</button>
</form>
<span id="temp"></span>
</details>
<p>Total image size: {{series.get_total_image_size|filesizeformat}}</p>
<p><a href="{% url 'atlas:series_download' pk=series.pk %}">Download images</a></p>
</details>
<span id="diff-controls">show <span id="diff-show-all" _="on click
log '1'
for row in .diff.rows
show row
end">all</span> <span id="diff-show-diff" _="
on click
log 'go'
for row in <.diff tr/>
log row.children[0].innerHTML
if row.children[0].innerHTML is empty
hide row
end
end
">diff</span></span>
<p>Total image size: {{series.get_total_image_size|filesizeformat}}</p>
<p><a href="{% url 'atlas:series_download' pk=series.pk %}">Download images</a></p>
</details>
<span id="diff-controls">show <span id="diff-show-all" _="on click
log '1'
for row in .diff.rows
show row
end">all</span> <span id="diff-show-diff" _="
on click
log 'go'
for row in <.diff tr/>
log row.children[0].innerHTML
if row.children[0].innerHTML is empty
hide row
end
end
">diff</span></span>
{% endif %}
<div id="clone-findings-modal"
class="modal modal-blur fade"
style="display: none"
aria-hidden="false"
tabindex="-1">
class="modal modal-blur fade"
style="display: none"
aria-hidden="false"
tabindex="-1">
<div class="modal-dialog modal-dialog-centered related-finding-modal" style="max-width: 700px;">
<div class="modal-content"></div>
</div>
@@ -284,6 +300,17 @@
cornerstone.resize(dicom_element, true);
});
$(".view-finding-button-3d").each((n, el) => {
$(el).click((e) => {
dicom_element = $(".cornerstone-element").get(0);
annotationjson3d = JSON.parse(e.currentTarget.dataset.annotationjson3d);
viewer_state_json = JSON.parse(e.currentTarget.dataset.viewerstatejson);
console.log("Loading 3D annotation and viewer state", annotationjson3d, viewer_state_json);
importAnnotations_root(annotationjson3d);
importViewerState_root(viewer_state_json);
$(el).addClass("active");
});
});
$(".view-finding-button").each((n, el) => {
$(el).click((e) => {
@@ -293,6 +320,11 @@
current_image_id_index = e.currentTarget.dataset.currentimageid;
console.log(e.currentTarget, current_image_id_index)
loadAnnotationAndViewportOnElement(annotationjson, viewport, dicom_element, current_image_id_index);
importLegacyViewport_root(viewport)
importLegacyAnnotations_root(annotationjson);
jumpToSliceByImageId_root(0, current_image_id_index);
console.log("Loaded annotation and viewport on element", dicom_element, annotationjson, viewport, current_image_id_index);
$(el).addClass("active");
@@ -368,6 +400,8 @@
annotation_json: JSON.stringify(cornerstoneTools.globalImageIdSpecificToolStateManager
.saveToolState()),
viewport_json: JSON.stringify(c.viewport),
annotation_json_3d: window.exportAnnotations_root(),
viewer_state_3d: window.exportViewerState_root(),
findings: JSON.stringify($('#finding-form select[name="findings"]').find(":selected")
.map((i, el) => {
return $(el).val()
+74
View File
@@ -0,0 +1,74 @@
{% load static %}
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
</head>
<body>
{% with image_url_array_and_count=series.get_image_url_array_and_count %}
{% comment %} <div id="single-dicom-viewer" class="dicom-viewer" data-images="{{ image_url_array_and_count.0 }}" data-annotations=''>
</div> {% endcomment %}
<div id="root" class="dicom-viewer-root" data-images="{{ image_url_array_and_count.0 }}"
style="width: 1000px; height: 600px; box-sizing: border-box; background: #222;"
></div>
{% endwith %}
<script type="module" src="{% static 'dv3d/index-DCNn6AQH.js' %}" defer="defer" type="module"></script>
<input type="file" id="dicomInput" multiple />
<button onclick="sendToViewer()">Send to Viewer</button>
<button onclick="testLoadWadouriStack()">Test Load wadouri Stack</button>
<script>
function sendToViewer() {
const files = document.getElementById('dicomInput').files;
if (window.loadDicomStackFromFiles) {
window.loadDicomStackFromFiles(files);
} else {
alert("Viewer API not ready");
}
}
function testLoadWadouriStack() {
const urls = [
"https://www.penracourses.org.uk/media/atlas/dicom/IMG_757_XQQyovo.dcm",
"https://www.penracourses.org.uk/media/atlas/dicom/IMG_766_8fWI0Qc.dcm",
"https://www.penracourses.org.uk/media/atlas/dicom/IMG_774_c8IpxUu.dcm",
"https://www.penracourses.org.uk/media/atlas/dicom/IMG_758_6WKI7ws.dcm",
"https://www.penracourses.org.uk/media/atlas/dicom/IMG_767_TduVOb8.dcm",
"https://www.penracourses.org.uk/media/atlas/dicom/IMG_775_7zWneQ0.dcm",
"https://www.penracourses.org.uk/media/atlas/dicom/IMG_759_BtKzgS5.dcm",
"https://www.penracourses.org.uk/media/atlas/dicom/IMG_768_RdvtM8K.dcm",
"https://www.penracourses.org.uk/media/atlas/dicom/IMG_776_LhVY3Ty.dcm",
"https://www.penracourses.org.uk/media/atlas/dicom/IMG_760_a8CiR4J.dcm",
"https://www.penracourses.org.uk/media/atlas/dicom/IMG_769_YHyJ1l6.dcm",
"https://www.penracourses.org.uk/media/atlas/dicom/IMG_777_8IFYJmL.dcm",
"https://www.penracourses.org.uk/media/atlas/dicom/IMG_761_vyvO1EN.dcm",
"https://www.penracourses.org.uk/media/atlas/dicom/IMG_770_vQwi4LW.dcm",
"https://www.penracourses.org.uk/media/atlas/dicom/IMG_778_BwGAMOR.dcm",
"https://www.penracourses.org.uk/media/atlas/dicom/IMG_762_DhSgQsm.dcm",
"https://www.penracourses.org.uk/media/atlas/dicom/IMG_771_xifitCl.dcm",
"https://www.penracourses.org.uk/media/atlas/dicom/IMG_779_LyA1zYn.dcm",
"https://www.penracourses.org.uk/media/atlas/dicom/IMG_763_aVjff3H.dcm",
"https://www.penracourses.org.uk/media/atlas/dicom/IMG_772_cDQZtxQ.dcm",
"https://www.penracourses.org.uk/media/atlas/dicom/IMG_780_qaZqsqg.dcm",
"https://www.penracourses.org.uk/media/atlas/dicom/IMG_764_dDoeldL.dcm",
"https://www.penracourses.org.uk/media/atlas/dicom/IMG_773_93Hvedg.dcm",
"https://www.penracourses.org.uk/media/atlas/dicom/IMG_781_bEx9Owg.dcm",
"https://www.penracourses.org.uk/media/atlas/dicom/IMG_765_qmd5mYJ.dcm"
];
const wadouriList = urls.map(url => "wadouri:" + url);
if (window.loadDicomStackFromWadouriList) {
window.loadDicomStackFromWadouriList(wadouriList);
} else {
alert("Viewer API not ready");
}
}
</script>
</body>
</html>
@@ -0,0 +1,47 @@
{% extends 'atlas/base.html' %}
{% block content %}
<h2>SeriesFinding Migration Status</h2>
<table class="table table-bordered table-sm">
<thead>
<tr>
<th>ID</th>
<th>Description</th>
<th>Series</th>
<th>annotation_json_3d</th>
<th>viewer_state_3d</th>
</tr>
</thead>
<tbody>
{% for f in findings_status %}
<tr>
<td>{{ f.id }}</td>
<td>{{ f.str }}</td>
<td>
{% if f.series %}
<a href="{% url 'atlas:series_detail' f.series.pk %}">{{ f.series }}</a>
{% else %}
<span class="text-muted">None</span>
{% endif %}
</td>
<td>
{% if f.annotation_json_3d %}
<span class="badge bg-success">Populated</span>
{% else %}
<span class="badge bg-danger">Empty</span>
{% endif %}
</td>
<td>
{% if f.viewer_state_3d %}
<span class="badge bg-success">Populated</span>
{% else %}
<span class="badge bg-danger">Empty</span>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock content %}