feat(reconstruction): Enhance volume reconstruction with geometry extraction and thickness merge options

This commit is contained in:
Ross
2026-05-17 11:19:18 +01:00
parent b6716c9ae1
commit c8d05818a4
2 changed files with 302 additions and 38 deletions
+59 -15
View File
@@ -250,16 +250,11 @@
<div class="card-header fw-semibold">Viewer / Compare Preview</div>
<div class="card-body p-2 d-flex flex-column gap-2">
<div id="truncate_viewer" class="dicom-viewer-root w-100"
style="height: 320px; background: #222;"
style="height: 460px; min-height: 360px; resize: vertical; overflow: auto; background: #222;"
data-images="{{ image_url_array_and_count.0 }}"
data-auto-cache-stack="false">
</div>
<div id="downsample-compare-wrap" class="d-none">
<div class="small text-muted mb-1">Downsample preview comparison</div>
<div id="downsample-compare-viewer" class="dicom-viewer-root w-100"
style="height: 240px; background: #222;"
data-auto-cache-stack="false"></div>
</div>
<div class="small text-muted">Downsample compare preview loads into this viewer as stack options.</div>
</div>
</div>
</div>
@@ -350,6 +345,15 @@
<label class="form-label small mb-1" for="recon-slice-spacing">Slice spacing (mm)</label>
<input type="number" step="0.1" min="0.1" id="recon-slice-spacing" name="recon_slice_spacing" class="form-control form-control-sm" placeholder="e.g. 1.0">
</div>
<div class="col-12">
<label class="form-label small mb-1" for="recon-thickness-mode">Thickness merge mode</label>
<select id="recon-thickness-mode" name="recon_thickness_mode" class="form-select form-select-sm">
<option value="mean" selected>Mean (default)</option>
<option value="max">Max intensity</option>
<option value="min">Min intensity</option>
</select>
<small class="text-muted">Thickness controls slab merge depth. Spacing controls distance between generated slices.</small>
</div>
</div>
<div class="mt-2">
@@ -385,6 +389,7 @@
const seriesPk = {{ series.pk }};
const totalImages = {{ image_url_array_and_count.1 }};
const viewerHeightKey = `series_viewer_height_${seriesPk}`;
const truncateViewerHeightKey = `truncate_viewer_height_${seriesPk}`;
// Restore viewer height from localStorage
function restoreViewerHeight() {
@@ -438,10 +443,46 @@
endHidden.value = endInput.value;
}
function getTruncateViewerElement() {
return document.getElementById(truncateApiKey);
}
function persistTruncateViewerHeight() {
const modalViewer = getTruncateViewerElement();
if (!modalViewer) {
return;
}
const height = window.getComputedStyle(modalViewer).height;
localStorage.setItem(truncateViewerHeightKey, height);
}
function applyTruncateViewerHeight(el) {
if (!el) {
return;
}
const saved = localStorage.getItem(truncateViewerHeightKey);
if (saved) {
el.style.height = saved;
}
}
function observeTruncateViewerResize() {
const modalViewer = getTruncateViewerElement();
if (!modalViewer) {
return;
}
applyTruncateViewerHeight(modalViewer);
const observer = new ResizeObserver(() => {
persistTruncateViewerHeight();
});
observer.observe(modalViewer);
}
// Modal optimization handlers
const truncateModal = document.getElementById('truncate-series-modal');
if (truncateModal) {
truncateModal.addEventListener('show.bs.modal', function () {
observeTruncateViewerResize();
setTimeout(() => {
try {
if (window.mountDicomViewers) {
@@ -530,24 +571,27 @@
return;
}
const compareWrap = document.getElementById('downsample-compare-wrap');
const compareViewer = document.getElementById('downsample-compare-viewer');
if (!compareWrap || !compareViewer) {
const modalViewer = getTruncateViewerElement();
if (!modalViewer) {
return;
}
compareWrap.classList.remove('d-none');
const existingHeight = window.getComputedStyle(modalViewer).height;
// Recreate element to force a clean mount with new stacks.
// Recreate the same modal viewer element to force a clean remount with compare stacks.
const fresh = document.createElement('div');
fresh.id = 'downsample-compare-viewer';
fresh.id = truncateApiKey;
fresh.className = 'dicom-viewer-root w-100';
fresh.style.height = '240px';
fresh.style.height = existingHeight;
fresh.style.minHeight = '360px';
fresh.style.resize = 'vertical';
fresh.style.overflow = 'auto';
fresh.style.background = '#222';
fresh.setAttribute('data-auto-cache-stack', 'false');
fresh.setAttribute('data-named-stacks', namedStacks);
compareViewer.replaceWith(fresh);
modalViewer.replaceWith(fresh);
observeTruncateViewerResize();
try {
if (window.mountDicomViewers) {