A modern, high-performance whole-slide image (WSI) viewer for digital pathology, built with FastAPI, Vue.js, and OpenSeadragon.
- π¬ High-Performance Viewing: Smooth pan/zoom of gigapixel pathology images using OpenSeadragon
- π Smart Scale Bar: Automatic scale bar with Β΅m/mm measurements based on slide metadata
- ποΈ File Browser: Hierarchical folder navigation with search and filtering
- πΌοΈ Thumbnails: Fast preview generation with viewport-aware lazy loading and concurrency limits
- π Metadata Display: View slide properties, scanner info, resolution, and associated images
- π Redis Caching: Shared Redis backend for tiles, thumbnails, directory trees, and path resolution across workers
- πΎ Fallback Cache: Local pickle-based LRU path cache if Redis is disabled
- π― Modern Stack: Python 3.13, FastAPI, Vue.js 3, and containerized deployment
- π Production Ready: Docker setup with health checks, non-root user, and optimized builds
Click to view screenshots
Browse slides with thumbnails and file information
Pan/zoom with scale bar, metadata panel, and associated images
- Clone the repository
git clone https://github.com/KatherLab/wsi-browser.git
cd wsi-browser
- Configure your slide directories
Edit docker-compose.yml
to mount your slide directories:
volumes:
- /path/to/your/slides:/path/to/your/slides:ro
Create a config.yml
file (you can copy config.example.yml
) to reference the mounted paths:
roots:
- path: "/path/to/your/slides"
label: "My Slides"
- Build and run
docker-compose build
docker-compose up -d
- Access the application
Open your browser to:
http://localhost:8010
- Install dependencies with uv
pip install uv
uv sync
- Configure
config.yml
roots:
- path: "/path/to/slides"
label: "Slide Collection"
cache:
enabled: true
redis_url: "redis://localhost:6379/0"
- Run the application
uv run uvicorn app.main:app --host 0.0.0.0 --port 8010 --reload
# Slide directories to expose in the UI
roots:
- path: "/data/slides"
label: "Research Slides"
- path: "/data/clinical"
label: "Clinical Cases"
# Files/folders to exclude
exclude:
- "__pycache__"
- "*.tmp"
- ".git"
# Supported slide formats
extensions:
- ".svs" # Aperio
- ".tif" # Generic TIFF
- ".tiff"
- ".ndpi" # Hamamatsu
- ".scn" # Leica
- ".mrxs" # Mirax (includes .mrxs sidecar directory)
- ".bif" # Ventana
# Redis caching configuration
cache:
enabled: true
redis_url: "redis://redis:6379/0" # Use "redis" hostname in Docker
ttl_seconds:
tree: 60 # Directory tree cache
thumb: 86400 # Thumbnail cache (24h)
tile: 3600 # Tile cache (1h)
# Thumbnail generation
thumbnails:
max_px: 512 # Maximum thumbnail dimension
prefer_associated: true # Use embedded thumbnails when available
# CORS settings
cors_allow_origins: ["*"] # Restrict in production
wsi-browser/
βββ app/
β βββ __init__.py
β βββ main.py # FastAPI application
β βββ cache.py # Redis caching layer
β βββ config.py # Configuration management
β βββ dz.py # Deep Zoom tile generation
β βββ fs_index.py # File system indexing
β βββ models.py # Pydantic models
β βββ thumbs.py # Thumbnail generation
β βββ path_cache.py # Redis + LRU path cache
β βββ templates/
β β βββ index.html # Vue.js frontend
β βββ static/
β βββ logo.svg # Optional branding
β βββ logo.png
βββ Dockerfile
βββ docker-compose.yml
βββ pyproject.toml
βββ config.yml
βββ README.md
Endpoint | Description |
---|---|
GET / |
Web UI |
GET /api/tree |
Directory tree structure |
GET /api/expand?path=... |
Expand a directory shallowly |
GET /api/dir?path=... |
List slides in directory |
GET /api/thumb/{slide_id} |
Slide thumbnail (with ETag caching) |
GET /api/meta/{slide_id} |
Slide metadata (with MIRAX sidecar size support) |
GET /api/associated/{slide_id} |
List associated images |
GET /api/associated/{slide_id}/{name} |
Get associated image |
GET /dzi/{slide_id}.dzi |
Deep Zoom descriptor (with ETag) |
GET /dzi/{slide_id}_files/{z}/{x}_{y}.jpeg |
Deep Zoom tiles (with ETag) |
GET /health |
Health check (reports Redis status) |
The application supports all formats readable by OpenSlide:
- Aperio (.svs, .tif)
- Hamamatsu (.ndpi, .vms, .vmu)
- Leica (.scn)
- MIRAX (.mrxs + sidecar directory)
- Philips (.tiff)
- Sakura (.svslide)
- Trestle (.tif)
- Ventana (.bif, .tif)
- Generic tiled TIFF (.tif, .tiff)
- Redis: Stores tiles, thumbnails, directory trees, and path lookups (shared across workers)
- LRU Fallback: Local pickle cache if Redis is disabled
- TTL Configuration: Customizable expiration times for tiles and thumbnails
- On-demand validation: Path entries checked for existence on access; stale entries evicted
- Multiple Workers: 4+ Uvicorn workers recommended
- Read-only Mounts: Slide directories mounted read-only
- Health Checks:
/health
endpoint for container monitoring - Non-root User: Enhanced security in containers
-
Set
maxmemory
andmaxmemory-policy allkeys-lru
in your Redis config to prevent out-of-memory errors:maxmemory 2gb maxmemory-policy allkeys-lru
-
Mount slides with
:ro
to enforce read-only access -
Tune NFS mount options for throughput:
nfsvers=3,rsize=262144,wsize=262144,hard,noatime
Slides not appearing
- Check file extensions in
config.yml
- Verify directory permissions
- Confirm Docker volume mounts
Performance issues
- Increase Redis memory limit in
docker-compose.yml
- Adjust worker count based on CPU cores
- Ensure NFS mount options are tuned (
noatime
, largerrsize/wsize
)
Connection errors
docker-compose ps
docker-compose logs -f
docker-compose exec redis redis-cli ping
curl http://localhost:8010/health
- Backend: modify
app/
Python modules - Frontend: edit
app/templates/index.html
- Rebuild:
docker-compose build && docker-compose up -d
uv sync --dev
uv run pytest
uv run ruff check app/
uv run mypy app/
- Restrict CORS origins in
config.yml
- Set up SSL/TLS termination (nginx/traefik)
- Configure monitoring (Prometheus/Grafana)
- Use log aggregation
- Add authentication if needed
- Set up Redis persistence/backups if required
- External Redis cluster for large deployments
- Multiple app instances behind load balancer
- CDN for static assets
- Distributed file system (NFS/GlusterFS) for slides
This project is licensed under the MIT License - see the LICENSE file for details.
- OpenSlide - C library for reading WSI files
- OpenSeadragon - Deep zoom image viewer
- FastAPI - Modern Python web framework
- Vue.js - Progressive JavaScript framework
For issues and questions:
- Open an issue on GitHub
- Check existing issues for solutions
- Provide logs and configuration when reporting bugs