# Multi-Viewport DICOM Viewer Implementation Plan

**Date**: 2025-01-13
**Status**: Implementation Plan

## Overview

Implement a multi-viewport DICOM viewer supporting 2x2 and 3x3 grid layouts, allowing users to view multiple DICOM series simultaneously with synchronized scrolling and independent viewport controls.

---

## Architecture Changes

### 1. New Class: `Viewport`

Extract current single-viewport logic into a reusable `Viewport` class.

**File**: `src/viewers/dicom/viewport.js`

**Responsibilities**:
- Manage a single Cornerstone-enabled canvas
- Handle viewport state (zoom, pan, window/level, rotation, invert)
- Load and display DICOM images
- Provide viewport manipulation methods

**Key Properties**:
```javascript
{
  id: number,                    // Viewport index (0-3 for 2x2, 0-8 for 3x3)
  element: HTMLCanvasElement,    // Cornerstone-enabled canvas
  seriesId: string|null,         // Currently loaded series
  currentImageIndex: number,      // Current image in series
  seriesDocIds: Array,           // Document IDs in series
  viewport: Object,              // Cornerstone viewport state
  linked: boolean,               // Synchronized scrolling enabled
  seriesMetadata: Object         // Folder/series info
}
```

**Key Methods**:
- `enable()` - Enable Cornerstone on canvas
- `disable()` - Disable and cleanup
- `loadSeries(seriesId, docIds, metadata)` - Load a series
- `loadImage(index)` - Load specific image from series
- `nextImage()` / `previousImage()` - Navigate series
- `setViewport(viewport)` - Update viewport state
- `getViewport()` - Get current viewport state
- `clear()` - Clear viewport content
- `updateOverlay()` - Update viewport overlay info

---

### 2. New Class: `ViewportManager`

Manages multiple viewports and grid layout.

**File**: `src/viewers/dicom/viewport-manager.js`

**Responsibilities**:
- Create and destroy viewports based on layout
- Track active viewport
- Handle layout switching (1x1, 2x2, 3x3)
- Manage synchronized scrolling
- Coordinate viewport interactions

**Key Properties**:
```javascript
{
  viewports: Array<Viewport>,    // All viewport instances
  activeViewportId: number,       // Currently focused viewport
  layoutMode: string,             // '1x1', '2x2', '3x3'
  linkedMode: boolean,            // Global link toggle
  gridContainer: HTMLElement      // Grid container element
}
```

**Key Methods**:
- `setLayout(mode)` - Switch layout (1x1, 2x2, 3x3)
- `createViewport(index)` - Create a new viewport
- `destroyViewport(index)` - Cleanup viewport
- `setActiveViewport(index)` - Set focus
- `getActiveViewport()` - Get active viewport
- `loadSeriesIntoViewport(viewportId, seriesId, docIds, metadata)` - Load series
- `clearViewport(viewportId)` - Clear viewport
- `swapViewports(id1, id2)` - Swap viewport contents
- `toggleLinkedMode()` - Enable/disable sync scrolling
- `onViewportScroll(viewportId, imageIndex)` - Handle scroll events
- `syncViewports(sourceId, imageIndex)` - Sync other viewports

---

### 3. Refactor: `DicomViewer`

Refactor main viewer class to use ViewportManager.

**File**: `src/viewers/dicom/viewer.js`

**Changes**:
- Replace single canvas/viewport logic with ViewportManager
- Update UI event handlers to work with multiple viewports
- Add layout selector UI
- Add viewport interaction UI
- Keep existing folder browser and analysis panel

**New Properties**:
```javascript
{
  viewportManager: ViewportManager,
  layoutMode: string,
  // Remove single viewport properties:
  // - this.element
  // - this.viewport
  // - this.seriesDocIds
  // - this.currentImageIndex
}
```

---

## UI Changes

### 1. HTML Structure (`viewer.html`)

**Replace single canvas with grid container**:

```html
<div class="viewport-grid-container" id="viewport-grid-container">
  <!-- Layout: 1x1 -->
  <div class="viewport-grid layout-1x1" id="viewport-grid" data-layout="1x1">
    <div class="viewport-cell" data-viewport-id="0">
      <canvas class="dicom-canvas" id="canvas-0"></canvas>
      <div class="viewport-overlay">
        <div class="viewport-label">Viewport 1</div>
        <div class="viewport-series-info">
          <span class="series-name">-</span>
        </div>
        <div class="viewport-image-info">
          <span class="image-counter">-</span>
        </div>
      </div>
      <div class="viewport-empty-state">
        <div class="empty-icon">📂</div>
        <div class="empty-text">Click a series to load</div>
      </div>
    </div>
  </div>
</div>
```

**Layout selector in toolbar**:

```html
<div class="layout-controls">
  <div class="layout-label">Layout:</div>
  <button class="layout-btn active" data-layout="1x1" title="Single viewport">1×1</button>
  <button class="layout-btn" data-layout="2x2" title="2×2 grid">2×2</button>
  <button class="layout-btn" data-layout="3x3" title="3×3 grid">3×3</button>
</div>

<div class="divider"></div>

<div class="viewport-controls">
  <button class="tool-btn" id="link-viewports-btn" title="Link viewports (synchronized scrolling)">🔗</button>
  <button class="tool-btn" id="swap-viewports-btn" title="Swap viewports">⇄</button>
  <button class="tool-btn" id="clear-viewport-btn" title="Clear active viewport">✕</button>
</div>
```

---

### 2. CSS Styles (`viewer.html` <style>)

**Viewport Grid Layouts**:

```css
.viewport-grid-container {
  flex: 1;
  position: relative;
  overflow: hidden;
}

.viewport-grid {
  display: grid;
  gap: 2px;
  width: 100%;
  height: 100%;
  background: #0a0a0a;
}

/* 1x1 Layout */
.viewport-grid.layout-1x1 {
  grid-template-columns: 1fr;
  grid-template-rows: 1fr;
}

/* 2x2 Layout */
.viewport-grid.layout-2x2 {
  grid-template-columns: 1fr 1fr;
  grid-template-rows: 1fr 1fr;
}

/* 3x3 Layout */
.viewport-grid.layout-3x3 {
  grid-template-columns: 1fr 1fr 1fr;
  grid-template-rows: 1fr 1fr 1fr;
}

.viewport-cell {
  position: relative;
  background: #1a1a1a;
  border: 2px solid #333;
  overflow: hidden;
  cursor: crosshair;
}

.viewport-cell.active {
  border-color: #0066cc;
  box-shadow: 0 0 8px rgba(0, 102, 204, 0.5);
}

.viewport-cell canvas {
  width: 100%;
  height: 100%;
  display: block;
}

/* Viewport Overlay */
.viewport-overlay {
  position: absolute;
  top: 8px;
  left: 8px;
  right: 8px;
  pointer-events: none;
  font-size: 11px;
  color: #fff;
  text-shadow: 0 0 4px rgba(0, 0, 0, 0.8);
}

.viewport-label {
  font-weight: 600;
  margin-bottom: 4px;
}

.viewport-series-info,
.viewport-image-info {
  font-size: 10px;
  color: #ccc;
}

/* Empty State */
.viewport-empty-state {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  text-align: center;
  color: #666;
  pointer-events: none;
}

.viewport-empty-state.hidden {
  display: none;
}

.empty-icon {
  font-size: 48px;
  margin-bottom: 8px;
  opacity: 0.5;
}

.empty-text {
  font-size: 12px;
}

/* Layout Controls */
.layout-controls {
  display: flex;
  align-items: center;
  gap: 4px;
}

.layout-label {
  font-size: 11px;
  color: #888;
  margin-right: 4px;
}

.layout-btn {
  background: #2d2d2d;
  border: 1px solid #444;
  color: #fff;
  padding: 6px 10px;
  border-radius: 4px;
  cursor: pointer;
  font-size: 11px;
  font-weight: 500;
  transition: all 0.2s ease;
}

.layout-btn:hover {
  background: #3d3d3d;
  border-color: #0066cc;
}

.layout-btn.active {
  background: #0066cc;
  border-color: #0066cc;
}

/* Viewport Controls */
.viewport-controls {
  display: flex;
  gap: 8px;
}

#link-viewports-btn.active {
  background: #0066cc;
  border-color: #0066cc;
}
```

---

### 3. Folder Browser Integration

**Add viewport selection to folder browser**:

Update folder item click behavior to load into active viewport.

**Add right-click context menu**:

```html
<div class="viewport-context-menu" id="viewport-context-menu" style="display: none;">
  <div class="context-menu-item" data-viewport="0">Load in Viewport 1</div>
  <div class="context-menu-item" data-viewport="1">Load in Viewport 2</div>
  <div class="context-menu-item" data-viewport="2">Load in Viewport 3</div>
  <div class="context-menu-item" data-viewport="3">Load in Viewport 4</div>
</div>
```

**Add drag-and-drop**:
- Drag folder item from browser
- Drop onto viewport to load series

---

## Implementation Phases

### Phase 1: Core Viewport Classes ✅
1. Create `Viewport` class (extract from DicomViewer)
2. Create `ViewportManager` class
3. Unit test viewport creation/destruction

### Phase 2: Layout System ✅
1. Update `viewer.html` with grid structure
2. Add CSS for 1x1, 2x2, 3x3 layouts
3. Implement layout switching
4. Add layout selector UI

### Phase 3: Series Loading ✅
1. Update folder browser to load into active viewport
2. Implement viewport focus/activation
3. Add empty state UI for viewports
4. Test series loading across viewports

### Phase 4: Synchronized Scrolling ✅
1. Implement linked mode toggle
2. Add scroll event coordination
3. Handle image index synchronization
4. Test with different series lengths

### Phase 5: Viewport Actions ✅
1. Implement clear viewport
2. Implement swap viewports
3. Add keyboard shortcuts (1-4 for viewport focus)
4. Add right-click context menu

### Phase 6: Advanced Features 🔄
1. Drag-and-drop from folder browser
2. Save/restore workspace layouts
3. Cross-reference lines (show slice position)
4. Hanging protocols (auto-arrange series)

---

## File Structure

```
src/viewers/dicom/
├── viewer.js                 # Main viewer (refactored to use ViewportManager)
├── viewport.js               # NEW: Single viewport class
├── viewport-manager.js       # NEW: Multi-viewport manager
├── viewer.html              # Updated with grid layout
├── dicom-processor.js       # Existing (no changes)
├── claude-radiology-api.js  # Existing (no changes)
├── radiology-analyzer.js    # Existing (no changes)
└── lib/                     # Existing (no changes)
```

---

## API Changes

### URL Parameters

Add layout parameter:

```
viewer.html?projectId=123&layout=2x2&vp0=series-001&vp1=series-002&vp2=series-003&vp3=series-004
```

**Parameters**:
- `layout`: `1x1`, `2x2`, `3x3`
- `vp0`, `vp1`, `vp2`, `vp3`, `vp4`, ..., `vp8`: Series IDs or folder IDs for each viewport

### Message Types

Add new message types to `constants.js`:

```javascript
LOAD_SERIES_INTO_VIEWPORT: 'load_series_into_viewport',
GET_VIEWPORT_STATE: 'get_viewport_state',
SET_VIEWPORT_STATE: 'set_viewport_state',
SWAP_VIEWPORTS: 'swap_viewports',
CLEAR_VIEWPORT: 'clear_viewport'
```

---

## Keyboard Shortcuts

Add new shortcuts:

- `1`, `2`, `3`, `4`, `5`, `6`, `7`, `8`, `9` - Activate viewport 1-9
- `L` - Toggle linked mode (synchronized scrolling)
- `Shift+1-4` - Load selected folder into viewport 1-4
- `Ctrl+Shift+C` - Clear active viewport
- `Ctrl+Shift+S` - Swap viewports (prompt for two viewport numbers)

---

## State Management

### ViewportManager State

Store viewport state for save/restore:

```javascript
{
  layout: '2x2',
  linkedMode: true,
  activeViewportId: 0,
  viewports: [
    {
      id: 0,
      seriesId: 'series-001',
      currentImageIndex: 5,
      viewport: { scale: 1.2, pan: {x: 0, y: 0}, ... },
      seriesMetadata: { folderName: 'T1 Weighted', ... }
    },
    // ... more viewports
  ]
}
```

### LocalStorage

Save workspace state to LocalStorage for persistence:

```javascript
// Save
localStorage.setItem('dicom-viewer-workspace', JSON.stringify(state));

// Restore
const state = JSON.parse(localStorage.getItem('dicom-viewer-workspace'));
viewportManager.restoreState(state);
```

---

## Performance Considerations

### Memory Management

- Lazy load images: Only load images when viewport becomes visible
- Unload off-screen viewports: Disable Cornerstone when viewport not visible
- Cache management: Limit total cached images across all viewports
- Viewport prioritization: Active viewport gets priority for image loading

### Optimization Strategies

1. **Batch Operations**: Load multiple viewport images in parallel (but throttle to avoid overwhelming API)
2. **Viewport Pooling**: Reuse Cornerstone elements when switching layouts
3. **Smart Rendering**: Skip render updates for non-visible viewports
4. **Debounce Events**: Throttle synchronized scrolling events

---

## Testing Plan

### Unit Tests

- Viewport creation/destruction
- Layout switching (1x1 → 2x2 → 3x3)
- Series loading into specific viewport
- Viewport state save/restore
- Synchronized scrolling logic

### Integration Tests

- Load 4 different series into 2x2 grid
- Switch layout with loaded series
- Synchronized scrolling with different series lengths
- Clear and reload viewports
- Swap viewport contents

### User Acceptance Tests

- Radiologist workflow: Load T1, T2, FLAIR, DWI into 2x2 grid
- Compare left vs right anatomical structures
- Scroll through synchronized series
- Focus viewport and use keyboard shortcuts
- Save and restore workspace

---

## Migration Strategy

### Backward Compatibility

- Single viewport mode (1x1) should work exactly as before
- Existing URL parameters (file, seriesId, projectId) still work
- No breaking changes to folder browser or analysis panel

### Progressive Enhancement

1. **Phase 1**: Ship with 1x1 and 2x2 layouts
2. **Phase 2**: Add 3x3 layout after 2x2 is stable
3. **Phase 3**: Add advanced features (drag-drop, hanging protocols)

---

## Success Metrics

### Performance

- Layout switch < 200ms
- Series load into viewport < 1s (cached) / < 3s (download)
- Synchronized scroll latency < 50ms
- Memory usage < 500MB for 4 viewports with 50-image series

### Usability

- User can load 4 series into 2x2 grid in < 10 seconds
- Keyboard navigation works intuitively
- Viewport focus is visually clear
- Empty state is helpful

---

## Future Enhancements

### Phase 7+ (Post-MVP)

1. **Custom Layouts**: 1x2, 1x3, 2x1, etc.
2. **Lightbox Mode**: Show 25 images in 5x5 grid from single series
3. **Picture-in-Picture**: Small overlay viewports
4. **Cross-Reference Lines**: Show slice position across viewports
5. **Hanging Protocols**: Auto-arrange series by DICOM tags
6. **Workspace Presets**: Save/load custom arrangements
7. **MPR (Multi-Planar Reconstruction)**: Show axial, sagittal, coronal simultaneously
8. **3D Volume Rendering**: 3D view in one viewport

---

## Notes

- **Cornerstone Limitations**: Each viewport needs its own enabled canvas element
- **Browser Performance**: Testing needed with 9 viewports (3x3) with large series
- **Touch Support**: Consider tablet/touch gestures for viewport interaction
- **Accessibility**: Ensure keyboard navigation works for all viewport actions

---

## Questions / Decisions Needed

1. ✅ Should we support 1x3 or 2x1 layouts initially? **Decision: Start with 1x1, 2x2, 3x3**
2. ✅ How to handle viewports with different window/level settings in linked mode? **Decision: Only sync image index, not viewport settings**
3. ✅ Should synchronized scrolling work with series of different lengths? **Decision: Yes, use proportional scrolling (e.g., 50% through each series)**
4. ✅ Drag-and-drop priority? **Decision: Phase 5**
5. ✅ Save workspace state to LocalStorage or URL params? **Decision: Both - LocalStorage for persistence, URL for sharing**

---

**End of Plan**
