feat(reconstruction): Enhance volume reconstruction with geometry extraction and thickness merge options
This commit is contained in:
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user