feat: add Compare button for side-by-side study comparison

Adds compareStudies() to viewer.js:
- Switches to 2-panel layout
- Loads most recent study (studies[0]) in left panel
- Loads prior study (studies[1]) in right panel
- Auto-matches same orientation series (AX > SAG > COR)
- Enables sync scroll automatically
- Jumps both panels to middle slice

Adds 'Compare' button to viewer toolbar (viewer/main.go) next to
1/2/3 Panel and 3D buttons.

Sync scroll already worked across studies via slice_location matching —
this just makes the workflow one click.
This commit is contained in:
James 2026-03-23 12:14:02 -04:00
parent ade93669d3
commit f2e352ebcf
2 changed files with 53 additions and 0 deletions

View File

@ -748,6 +748,58 @@ async function setPanels(count) {
}
}
// Compare mode: 2-panel split with current + prior study, same orientation,
// sync scroll enabled. If only one study exists, loads same study in both panels.
async function compareStudies() {
if (studies.length < 1) return;
is3DMode = false;
document.getElementById('studySelect3d').style.display = 'none';
document.getElementById('panels').innerHTML = '';
panels = [];
panelCount = 0;
const studyA = studies[0]; // most recent
const studyB = studies.length > 1 ? studies[1] : studyA; // prior (or same)
// Load both panels
await addPanel();
await addPanel();
// Load study A into panel 0
currentStudyId = studyA.id;
await changeStudyForPanel(0, studyA.id);
// Load study B into panel 1
await changeStudyForPanel(1, studyB.id);
// After both are loaded, try to match orientation: prefer AX, then SAG, then COR
const preferOri = ['AX', 'SAG', 'COR'];
for (const ori of preferOri) {
const sA = (panels[0].seriesList || []).find(s => s.orientation === ori || (s.series_desc || '').toUpperCase().startsWith(ori));
const sB = (panels[1].seriesList || []).find(s => s.orientation === ori || (s.series_desc || '').toUpperCase().startsWith(ori));
if (sA && sB) {
const selA = document.getElementById('panel-0').querySelector('.series-select');
const selB = document.getElementById('panel-1').querySelector('.series-select');
if (selA) selA.value = sA.id;
if (selB) selB.value = sB.id;
await loadSeries(0, sA.id);
await loadSeries(1, sB.id);
break;
}
}
// Enable sync scroll
const syncEl = document.getElementById('syncScroll');
if (syncEl) syncEl.checked = true;
// Sync both panels to middle slice
if (panels[0] && panels[0].slices.length) {
const mid = Math.floor(panels[0].slices.length / 2);
goToSlice(0, mid);
}
}
async function changeStudyForPanel(panelIdx, studyId) {
currentStudyId = studyId;
const panel = panels[panelIdx];

View File

@ -389,6 +389,7 @@ func handleViewer(w http.ResponseWriter, r *http.Request) {
<button id="btn2panels" onclick="setPanels(2)">2 Panels</button>
<button id="btn3panels" onclick="setPanels(3)">3 Panels</button>
<button id="btn3d" onclick="set3DMode()">3D</button>
<button id="btnCompare" onclick="compareStudies()" title="Compare current vs prior study side-by-side with sync scroll">Compare</button>
</div>
<label id="syncLabel" class="sync-label"><input type="checkbox" id="syncScroll" checked><span>Sync</span></label>
</div>