try to fix recons
This commit is contained in:
@@ -33,7 +33,7 @@
|
||||
</div>
|
||||
|
||||
<div class="alert alert-info recon-advanced-help">
|
||||
<div><strong>Workflow:</strong> click <em>Apply MPR layout</em>, switch all panes to volume mode if needed, enable crosshairs in the DV3D toolbar, then position the crosshair planes as desired before generating reconstructions.</div>
|
||||
<div><strong>Workflow:</strong> the page auto-configures a 3-volume MPR layout on load and attempts to enable crosshair-linked navigation. Adjust crosshairs as needed, then generate reconstructions.</div>
|
||||
</div>
|
||||
|
||||
<div id="advanced_recon_root" class="dicom-viewer-root recon-advanced-viewer" data-images='{{ image_ids_json|safe }}'></div>
|
||||
@@ -72,10 +72,11 @@
|
||||
|
||||
<div class="col-12 col-lg-4">
|
||||
<label class="form-label" for="adv-recon-thickness">Slab thickness (mm)</label>
|
||||
<input id="adv-recon-thickness" type="number" step="0.1" min="0.1" name="recon_slice_thickness" class="form-control" placeholder="e.g. 2.5">
|
||||
<input id="adv-recon-thickness" type="number" step="0.1" min="0.1" name="recon_slice_thickness" class="form-control" value="3.0">
|
||||
|
||||
<label class="form-label mt-2" for="adv-recon-spacing">Slice spacing (mm)</label>
|
||||
<input id="adv-recon-spacing" type="number" step="0.1" min="0.1" name="recon_slice_spacing" class="form-control" placeholder="e.g. 1.0">
|
||||
<input id="adv-recon-spacing" type="number" step="0.1" min="3" name="recon_slice_spacing" class="form-control" value="3.0">
|
||||
<small class="text-muted d-block mt-1">Spacing is user-controlled and defaults to 3.0 mm minimum to reduce generated stack size.</small>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-lg-4">
|
||||
@@ -99,36 +100,176 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card border-0 shadow-sm mt-3">
|
||||
<div class="card-body">
|
||||
<div class="d-flex flex-wrap justify-content-between align-items-center gap-2 mb-2">
|
||||
<h6 class="mb-0">Live Reconstruction Plan</h6>
|
||||
<small id="advanced-plan-status" class="text-muted">Waiting for viewer telemetry...</small>
|
||||
</div>
|
||||
<div class="row g-2 small">
|
||||
<div class="col-12 col-md-4"><strong>Viewport 1:</strong> <span id="advanced-plan-vp0">-</span></div>
|
||||
<div class="col-12 col-md-4"><strong>Viewport 2:</strong> <span id="advanced-plan-vp1">-</span></div>
|
||||
<div class="col-12 col-md-4"><strong>Viewport 3:</strong> <span id="advanced-plan-vp2">-</span></div>
|
||||
</div>
|
||||
<div class="row g-2 small mt-1">
|
||||
<div class="col-12 col-md-6"><strong>Slab thickness:</strong> <span id="advanced-plan-thickness">3.0 mm</span></div>
|
||||
<div class="col-12 col-md-6"><strong>Slice spacing:</strong> <span id="advanced-plan-spacing">3.0 mm</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="advanced-recon-feedback" class="small mt-3"></div>
|
||||
</div>
|
||||
|
||||
<script src="{% static 'dv3d/index.js' %}" type="module" defer="defer"></script>
|
||||
<script>
|
||||
(function () {
|
||||
var ORIENTATION_LABELS = {
|
||||
axial: 'Axial',
|
||||
coronal: 'Coronal',
|
||||
sagittal: 'Sagittal',
|
||||
};
|
||||
|
||||
function getViewerApi(name) {
|
||||
return window[name + '_advanced_recon_root'] || window[name + '_advanced_recon'] || window[name + '_root'] || window[name];
|
||||
}
|
||||
|
||||
function buildMprState() {
|
||||
return {
|
||||
function parseViewerState(raw) {
|
||||
if (!raw) {
|
||||
return null;
|
||||
}
|
||||
if (typeof raw === 'string') {
|
||||
try {
|
||||
return JSON.parse(raw);
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return raw;
|
||||
}
|
||||
|
||||
function getDisplayOrientation(value) {
|
||||
if (!value) {
|
||||
return 'Unknown';
|
||||
}
|
||||
return ORIENTATION_LABELS[value] || String(value).charAt(0).toUpperCase() + String(value).slice(1);
|
||||
}
|
||||
|
||||
function ensureMprLayout() {
|
||||
var exportFn = getViewerApi('exportViewerState');
|
||||
var importFn = getViewerApi('importViewerState');
|
||||
if (typeof importFn !== 'function') {
|
||||
return false;
|
||||
}
|
||||
|
||||
var existing = parseViewerState(typeof exportFn === 'function' ? exportFn() : null) || {};
|
||||
var stackIdx = Array.isArray(existing.stackIdx) ? existing.stackIdx : [];
|
||||
var stacks = Array.isArray(existing.stacks) ? existing.stacks : [];
|
||||
var baseStack = typeof stackIdx[0] === 'number' ? stackIdx[0] : (stacks.length > 0 ? 0 : null);
|
||||
|
||||
var nextState = {
|
||||
grid: { rows: 1, cols: 3 },
|
||||
modes: ['volume', 'volume', 'volume'],
|
||||
orientations: ['axial', 'coronal', 'sagittal'],
|
||||
volumeSliceIndices: [null, null, null],
|
||||
};
|
||||
|
||||
if (stacks.length > 0 && baseStack !== null) {
|
||||
nextState.stacks = stacks;
|
||||
nextState.stackIdx = [baseStack, baseStack, baseStack];
|
||||
}
|
||||
|
||||
try {
|
||||
importFn(nextState);
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.warn('Failed to apply automatic MPR layout', err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function tryEnableCrosshairs() {
|
||||
var labels = ['Enable Cross Reference Lines', 'Enable Crosshairs'];
|
||||
var root = document.getElementById('advanced_recon_root');
|
||||
var scope = root ? root.parentElement || document : document;
|
||||
var buttons = scope.querySelectorAll('button');
|
||||
var clicked = false;
|
||||
|
||||
buttons.forEach(function (btn) {
|
||||
if (clicked) {
|
||||
return;
|
||||
}
|
||||
var text = (btn.textContent || '').trim();
|
||||
if (!text) {
|
||||
return;
|
||||
}
|
||||
for (var i = 0; i < labels.length; i++) {
|
||||
if (text.indexOf(labels[i]) !== -1) {
|
||||
btn.click();
|
||||
clicked = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return clicked;
|
||||
}
|
||||
|
||||
function formatViewportPlan(state, idx) {
|
||||
if (!state) {
|
||||
return '-';
|
||||
}
|
||||
var modes = Array.isArray(state.modes) ? state.modes : [];
|
||||
var orientations = Array.isArray(state.orientations) ? state.orientations : [];
|
||||
var slices = Array.isArray(state.volumeSliceIndices) ? state.volumeSliceIndices : [];
|
||||
var mode = modes[idx] || 'unknown';
|
||||
var orientation = getDisplayOrientation(orientations[idx]);
|
||||
var slice = slices[idx];
|
||||
var sliceText = typeof slice === 'number' ? ('slice ' + slice) : 'slice n/a';
|
||||
return orientation + ' (' + mode + ', ' + sliceText + ')';
|
||||
}
|
||||
|
||||
function updateLivePlan() {
|
||||
var exportFn = getViewerApi('exportViewerState');
|
||||
var thicknessInput = document.getElementById('adv-recon-thickness');
|
||||
var spacingInput = document.getElementById('adv-recon-spacing');
|
||||
var state = parseViewerState(typeof exportFn === 'function' ? exportFn() : null);
|
||||
|
||||
var statusEl = document.getElementById('advanced-plan-status');
|
||||
var vp0 = document.getElementById('advanced-plan-vp0');
|
||||
var vp1 = document.getElementById('advanced-plan-vp1');
|
||||
var vp2 = document.getElementById('advanced-plan-vp2');
|
||||
var thicknessEl = document.getElementById('advanced-plan-thickness');
|
||||
var spacingEl = document.getElementById('advanced-plan-spacing');
|
||||
|
||||
if (statusEl) {
|
||||
statusEl.textContent = state ? 'Viewer telemetry active' : 'Waiting for viewer telemetry...';
|
||||
}
|
||||
if (vp0) {
|
||||
vp0.textContent = formatViewportPlan(state, 0);
|
||||
}
|
||||
if (vp1) {
|
||||
vp1.textContent = formatViewportPlan(state, 1);
|
||||
}
|
||||
if (vp2) {
|
||||
vp2.textContent = formatViewportPlan(state, 2);
|
||||
}
|
||||
|
||||
if (thicknessEl) {
|
||||
var thicknessValue = thicknessInput && thicknessInput.value ? thicknessInput.value : '3.0';
|
||||
thicknessEl.textContent = thicknessValue + ' mm';
|
||||
}
|
||||
if (spacingEl) {
|
||||
var spacingValue = spacingInput && spacingInput.value ? spacingInput.value : '3.0';
|
||||
spacingEl.textContent = spacingValue + ' mm';
|
||||
}
|
||||
}
|
||||
|
||||
var applyBtn = document.getElementById('apply-mpr-layout');
|
||||
if (applyBtn) {
|
||||
applyBtn.addEventListener('click', function () {
|
||||
var importFn = getViewerApi('importViewerState');
|
||||
if (typeof importFn === 'function') {
|
||||
try {
|
||||
importFn(buildMprState());
|
||||
} catch (err) {
|
||||
console.warn('Failed to apply MPR layout', err);
|
||||
}
|
||||
}
|
||||
ensureMprLayout();
|
||||
setTimeout(updateLivePlan, 400);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -150,6 +291,30 @@
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var thicknessInput = document.getElementById('adv-recon-thickness');
|
||||
var spacingInput = document.getElementById('adv-recon-spacing');
|
||||
if (thicknessInput) {
|
||||
thicknessInput.addEventListener('input', updateLivePlan);
|
||||
}
|
||||
if (spacingInput) {
|
||||
spacingInput.addEventListener('input', updateLivePlan);
|
||||
}
|
||||
|
||||
var autoAttempts = 0;
|
||||
var autoTimer = setInterval(function () {
|
||||
autoAttempts += 1;
|
||||
var mprApplied = ensureMprLayout();
|
||||
var crosshairEnabled = tryEnableCrosshairs();
|
||||
updateLivePlan();
|
||||
|
||||
if ((mprApplied && crosshairEnabled) || autoAttempts >= 12) {
|
||||
clearInterval(autoTimer);
|
||||
}
|
||||
}, 600);
|
||||
|
||||
setInterval(updateLivePlan, 700);
|
||||
setTimeout(updateLivePlan, 1500);
|
||||
})();
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user