# DICOM Viewer - Complete Feature Documentation

**Date**: 2026-01-12
**Status**: ✅ PRODUCTION READY

---

## Overview

The DICOM viewer is a fully-featured medical imaging viewer built into the Filevine Chrome extension. It supports single images and multi-image series with comprehensive navigation, zoom, and brightness controls.

---

## Features

### Core Functionality

✅ **DICOM Image Rendering**
- Displays DICOM medical images using Cornerstone.js v2.6.1
- Supports 8-bit, 16-bit grayscale images
- Proper window/level (brightness/contrast) handling
- DICOM metadata display (patient name, study date, modality, image size)

✅ **Series Navigation**
- Automatically detects folders with multiple DICOM files
- Creates series in IndexedDB for persistent access
- Navigate through images with buttons, keyboard, or mouse scroll
- Displays current position (e.g., "3 / 24")
- Circular navigation (wraps around at first/last image)
- Consistent zoom level maintained across all images in series

✅ **Image Controls**
- **Zoom**: In/out with proper aspect ratio maintenance
- **Rotation**: 90° increments left/right
- **Invert**: Toggle black/white inversion
- **Fit to Screen**: Auto-scale to fit viewport
- **Reset**: Return to default view settings
- **Brightness**: Adjust window center (black/white balance)

✅ **User Interface**
- Clean, dark medical imaging interface
- Minimal chrome to maximize viewing area
- Info panel with patient/study metadata
- Status bar showing zoom level and window/level values
- Keyboard shortcuts help overlay
- Responsive layout

---

## Keyboard Shortcuts

### Mouse Controls

| Control | Action |
|---------|--------|
| **Scroll** | Navigate series (when viewing multiple images) |
| **Ctrl + Scroll** | Zoom in/out |
| **Shift + Scroll** | Adjust brightness (black/white balance) |

### Keyboard Shortcuts

| Key | Action |
|-----|--------|
| **+** or **=** | Zoom in |
| **-** or **_** | Zoom out |
| **0** | Fit to screen |
| **R** | Reset view to defaults |
| **I** | Invert colors (black ↔ white) |
| **← →** | Navigate series OR rotate image (single image) |
| **↑ ↓** | Navigate series (when viewing multiple images) |
| **PageUp** / **PageDown** | Navigate series |
| **?** | Show keyboard shortcuts help |
| **Escape** | Close help overlay |

---

## How to Use

### Viewing a Single DICOM Image

1. Navigate to a DICOM file in Filevine sidebar
2. Click the file to download and view
3. Viewer opens automatically with the image
4. Use toolbar buttons or keyboard shortcuts to manipulate

### Viewing a DICOM Series (Multiple Images)

1. Navigate to a folder containing DICOM files in Filevine
2. Click the **Download** button on the folder
3. Extension automatically:
   - Downloads all files in folder (including subfolders)
   - Detects DICOM files by extension (.dcm, .dicom) or MIME type
   - Creates a series if 2+ DICOM files found
   - Opens viewer with series navigation controls after 2 seconds
4. Navigate through images using:
   - **◀ ▶** buttons in toolbar
   - Mouse scroll wheel
   - Arrow keys or PageUp/PageDown

---

## Technical Architecture

### Data Storage

**IndexedDB Schema**:
```javascript
// Object store: 'files'
{
  docId: number,           // Primary key
  projectId: number,
  blob: Blob,             // File data
  metadata: {
    filename: string,
    mimeType: string,
    size: number,
    downloadedAt: timestamp
  }
}

// Object store: 'series'
{
  seriesId: string,       // Primary key: "series_<projectId>_<folderId>_<timestamp>"
  projectId: number,
  folderId: number,
  folderName: string,
  docIds: number[],       // Array of document IDs in order
  imageCount: number,
  createdAt: timestamp
}
```

### Viewport Management

**Proper Aspect Ratio**:
- Canvas explicitly sized to match parent container dimensions
- Scale calculated to fit image within canvas bounds
- Viewport translation always centered (0, 0)
- Consistent `seriesBaseScale` used across all images in a series

**Viewport Object**:
```javascript
{
  scale: number,              // Zoom level
  translation: { x: 0, y: 0 }, // Always centered
  voi: {
    windowWidth: number,      // Contrast range
    windowCenter: number      // Brightness midpoint
  },
  rotation: number,           // Degrees (0, 90, 180, 270)
  invert: boolean,           // Color inversion
  pixelReplication: false,
  hflip: false,
  vflip: false,
  modalityLUT: object,
  voiLUT: object
}
```

### Message Passing

**Background Script → Viewer**:
- Files transferred as data URLs (base64) because Blobs can't pass through Chrome message API
- Series metadata retrieved by seriesId

**Message Types**:
```javascript
// Get file for display
{ type: 'GET_FILE', docId: number }
→ { file: { dataUrl: string, metadata: object } }

// Get series metadata
{ type: 'GET_SERIES', seriesId: string }
→ { series: { seriesId, projectId, folderId, folderName, docIds[], imageCount } }

// Open series in viewer
{ type: 'OPEN_SERIES', seriesId: string }
```

---

## File Structure

```
src/viewers/dicom/
├── viewer.html              # Main viewer UI
├── viewer.js                # Viewer controller class
├── lib-loader.js            # Sequential library loader
└── lib/                     # Bundled libraries
    ├── dicomParser.min.js
    ├── cornerstone.min.js
    ├── cornerstoneMath.min.js
    ├── cornerstoneTools.min.js
    └── cornerstoneWADOImageLoader.min.js
```

---

## Key Implementation Details

### 1. Sequential Library Loading

Libraries load in strict dependency order to avoid race conditions:
```
dicomParser → cornerstone → cornerstoneMath → cornerstoneTools → cornerstoneWADOImageLoader
```

50ms delay between each load ensures globals are fully initialized.

### 2. Aspect Ratio Fix

**Problem**: Images were stretched horizontally due to incorrect canvas dimensions.

**Solution**:
```javascript
// Get parent dimensions
const parentWidth = parent.clientWidth;
const parentHeight = parent.clientHeight;

// Set canvas explicitly (in pixels)
canvas.style.width = parentWidth + 'px';
canvas.style.height = parentHeight + 'px';

// Force resize after enabling
cornerstone.enable(this.element);
cornerstone.resize(this.element, true);
```

### 3. Brightness Control (Shift+Scroll)

**Problem**: trackpad sends `-0` deltaY values that need special handling.

**Solution**:
```javascript
// Try multiple delta sources for hardware compatibility
let delta = e.deltaY;
if (delta === 0 || Object.is(delta, -0) || Object.is(delta, 0)) {
  delta = e.wheelDeltaY ? -e.wheelDeltaY : e.deltaX;
}

if (delta !== 0 && !Object.is(delta, 0) && !Object.is(delta, -0)) {
  const direction = delta > 0 ? -1 : 1;
  this.adjustBrightness(direction);
}
```

### 4. Series Consistency

**Problem**: Each image in a series had different zoom levels.

**Solution**: Store `seriesBaseScale` from first image and reuse for all subsequent images:
```javascript
if (this.seriesDocIds.length > 1 && this.seriesBaseScale !== null) {
  scale = this.seriesBaseScale;
} else {
  // Calculate scale for first image
  scale = canvasHeight / image.height * 0.98;

  if (this.seriesDocIds.length > 1) {
    this.seriesBaseScale = scale;
  }
}
```

---

## Browser Compatibility

**Tested On**:
- ✅ Chrome 143+ (Manifest V3)
- ✅ macOS trackpad gestures
- ✅ Mouse wheel scrolling

**Requirements**:
- Chrome Extension Manifest V3
- IndexedDB support
- ES6+ JavaScript

---

## Known Limitations

1. **No Mouse Pan/Drag**: Mouse pan/zoom disabled due to cornerstoneTools v6 compatibility issues. Use toolbar buttons instead.

2. **Series are Temporary**: Series stored in IndexedDB, cleared if extension uninstalled. No UI for managing old series.

3. **No Sorting**: Images displayed in API response order, not sorted by DICOM Instance Number or Acquisition Time.

4. **No Multi-frame Support**: Each `.dcm` file treated as single image. Multi-frame DICOM (videos/time-series) not supported.

5. **Memory Usage**: Each image loaded fresh from IndexedDB. No caching between navigations.

---

## Future Enhancements

### Short-term
- [ ] Sort series by DICOM metadata (Instance Number, Acquisition Time)
- [ ] Preload next/previous images for faster navigation
- [ ] Thumbnail filmstrip showing all images in series
- [ ] Measurement tools (distance, angle, ROI)

### Long-term
- [ ] Upgrade to Cornerstone3D for better performance
- [ ] 3D volume reconstruction from series
- [ ] MPR (Multi-Planar Reconstruction)
- [ ] Windowing presets (bone, soft tissue, lung, etc.)
- [ ] DICOM metadata viewer showing all tags
- [ ] Series manager UI to browse/delete old series
- [ ] Export series to video or animated GIF

---

## Troubleshooting

### Images appear stretched/distorted
**Cause**: Canvas aspect ratio mismatch.
**Fix**: Already resolved in current version. Reload viewer.

### Shift+Scroll doesn't adjust brightness
**Cause**: Trackpad sending zero deltas.
**Fix**: Already resolved - now handles `-0` deltas correctly. Reload viewer.

### Series doesn't open automatically after folder download
**Cause**: Files still downloading when viewer tried to open.
**Fix**: Auto-open has 2-second delay. Manually open via: `openSeries('series_id')` in console.

### Zoom doesn't maintain aspect ratio
**Cause**: Fixed in current version.
**Fix**: Reload viewer. Zoom now properly scales both dimensions.

---

## API Reference

### Background Script (downloads.js)

```javascript
// Store series metadata
await downloadManager.storeSeries({
  seriesId: 'series_1234_5678_timestamp',
  projectId: 1234,
  folderId: 5678,
  folderName: 'CT Scan',
  docIds: [101, 102, 103],
  imageCount: 3
});

// Retrieve series
const series = await downloadManager.getSeries('series_1234_5678_timestamp');
// Returns: { seriesId, projectId, folderId, folderName, docIds, imageCount, createdAt }

// Open series in viewer
await downloadManager.openSeries('series_1234_5678_timestamp');
// Opens: chrome-extension://.../viewer.html?seriesId=series_1234_5678_timestamp
```

### Viewer (viewer.js)

```javascript
// Load series
await viewer.loadSeries('series_1234_5678_timestamp');

// Navigate images
viewer.nextImage();     // Advance to next image
viewer.previousImage(); // Go to previous image

// Image controls
viewer.zoomIn();        // Zoom in by 1.2x
viewer.zoomOut();       // Zoom out by 1.2x
viewer.rotate(90);      // Rotate 90° clockwise
viewer.toggleInvert();  // Toggle color inversion
viewer.reset();         // Reset to defaults
viewer.fitToScreen();   // Scale to fit viewport

// Brightness adjustment
viewer.adjustBrightness(1);   // Increase brightness
viewer.adjustBrightness(-1);  // Decrease brightness

// Help overlay
viewer.showHelp();      // Show keyboard shortcuts
viewer.hideHelp();      // Hide help overlay
```

---

## Success Metrics

✅ **Feature Complete**:
- [x] Single image viewing
- [x] Series navigation (multiple images)
- [x] Mouse scroll navigation
- [x] Ctrl+Scroll zoom
- [x] Shift+Scroll brightness adjustment
- [x] Keyboard shortcuts
- [x] Help overlay
- [x] Proper aspect ratio handling
- [x] Consistent zoom across series
- [x] Auto-detection and series creation
- [x] IndexedDB persistence

✅ **User Experience**:
- [x] Intuitive controls
- [x] Responsive UI
- [x] Visual feedback (status bar, image counter)
- [x] Discoverable shortcuts (help overlay)
- [x] No distortion or stretching
- [x] Smooth navigation

✅ **Technical Quality**:
- [x] No console errors
- [x] Proper error handling
- [x] Clean code structure
- [x] Comprehensive documentation

---

## Summary

The DICOM viewer is production-ready with all core features implemented. Users can view single DICOM images or entire series with full navigation, zoom, brightness controls, and keyboard shortcuts. The viewer maintains proper aspect ratios, provides consistent zoom levels across series, and includes a help overlay for discoverability.

**Total Implementation**: ~500 lines of new code across 4 files
**Development Time**: Full-featured viewer built in single session
**Status**: Ready for production use

---

## Change Log

### 2026-01-12 - v1.0 (Production Release)

**Features Added**:
- ✅ DICOM series support (multiple images)
- ✅ Mouse scroll navigation
- ✅ Ctrl+Scroll zoom
- ✅ Shift+Scroll brightness adjustment
- ✅ Keyboard shortcuts help overlay
- ✅ Series counter (e.g., "3 / 24")
- ✅ Circular navigation with wraparound
- ✅ Consistent zoom across series

**Bugs Fixed**:
- ✅ Fixed aspect ratio distortion
- ✅ Fixed zoom stretching images
- ✅ Fixed Shift+Scroll not working (handled `-0` deltas)
- ✅ Fixed inconsistent zoom levels across series images
- ✅ Fixed canvas sizing issues

**Documentation**:
- ✅ Complete user guide
- ✅ Technical architecture documentation
- ✅ API reference
- ✅ Troubleshooting guide
