Python interface to OpenSlide for reading whole-slide images used in digital pathology
—
Generate Deep Zoom tiles and metadata from OpenSlide objects for web-based slide viewing. Deep Zoom provides smooth zooming and panning by creating a pyramid of pre-rendered tiles at multiple resolution levels, enabling efficient web-based visualization of large whole-slide images.
import openslide
from openslide.deepzoom import DeepZoomGenerator
from openslide import AbstractSlide
from PIL import ImageCreate Deep Zoom tile generators that convert whole-slide images into web-compatible tile pyramids.
from openslide.deepzoom import DeepZoomGenerator
import openslide
class DeepZoomGenerator:
"""
Generates Deep Zoom tiles and metadata from slide objects.
Creates a pyramid of tiles at multiple resolution levels for efficient
web-based viewing with smooth zooming and panning.
"""
def __init__(self,
osr: openslide.AbstractSlide,
tile_size: int = 254,
overlap: int = 1,
limit_bounds: bool = False):
"""
Create a DeepZoomGenerator for a slide.
Args:
osr: Slide object (OpenSlide, ImageSlide, or AbstractSlide)
tile_size: Width and height of individual tiles. For best performance,
tile_size + 2 * overlap should be a power of two (default: 254)
overlap: Number of extra pixels to add to each interior edge of tiles
for seamless viewing (default: 1)
limit_bounds: If True, only render the non-empty slide region based
on bounds properties (default: False)
"""
BOUNDS_OFFSET_PROPS = (
openslide.PROPERTY_NAME_BOUNDS_X,
openslide.PROPERTY_NAME_BOUNDS_Y,
)
BOUNDS_SIZE_PROPS = (
openslide.PROPERTY_NAME_BOUNDS_WIDTH,
openslide.PROPERTY_NAME_BOUNDS_HEIGHT,
)Access information about the Deep Zoom pyramid structure and tile organization.
class DeepZoomGenerator:
@property
def level_count(self) -> int:
"""
Number of Deep Zoom levels in the image.
Level 0 is the smallest (most zoomed out), highest level is largest.
"""
@property
def level_tiles(self) -> tuple[tuple[int, int], ...]:
"""
Number of tiles in each Deep Zoom level.
Returns tuple of (tiles_x, tiles_y) for each level.
level_tiles[n] contains tile counts for level n.
"""
@property
def level_dimensions(self) -> tuple[tuple[int, int], ...]:
"""
Pixel dimensions of each Deep Zoom level.
Returns tuple of (pixels_x, pixels_y) for each level.
level_dimensions[n] contains pixel dimensions for level n.
"""
@property
def tile_count(self) -> int:
"""
Total number of tiles across all Deep Zoom levels.
"""Generate individual tiles and tile coordinates for Deep Zoom viewing.
class DeepZoomGenerator:
def get_tile(self, level: int, address: tuple[int, int]) -> Image.Image:
"""
Generate a tile image for the specified level and address.
Args:
level: Deep Zoom level number (0 = most zoomed out)
address: Tile address as (col, row) tuple within the level
Returns:
PIL.Image in RGB format containing the tile
Raises:
ValueError: If level or address is invalid
"""
def get_tile_coordinates(self, level: int, address: tuple[int, int]) -> tuple[tuple[int, int], int, tuple[int, int]]:
"""
Get OpenSlide read_region() arguments for a tile.
Most users should use get_tile() instead of calling read_region() directly.
Args:
level: Deep Zoom level number
address: Tile address as (col, row) tuple
Returns:
Tuple of (location, slide_level, size) for OpenSlide.read_region()
"""
def get_tile_dimensions(self, level: int, address: tuple[int, int]) -> tuple[int, int]:
"""
Get pixel dimensions of a specific tile.
Args:
level: Deep Zoom level number
address: Tile address as (col, row) tuple
Returns:
(pixels_x, pixels_y) tuple for the tile
"""Generate Deep Zoom Image (DZI) XML metadata files for web viewers.
class DeepZoomGenerator:
def get_dzi(self, format: str) -> str:
"""
Generate XML metadata for the Deep Zoom Image (.dzi) file.
Args:
format: Image format for tiles ('png' or 'jpeg')
Returns:
XML string containing DZI metadata
"""import openslide
from openslide.deepzoom import DeepZoomGenerator
# Open a slide
with openslide.open_slide("slide.svs") as slide:
# Create Deep Zoom generator
dz = DeepZoomGenerator(slide, tile_size=254, overlap=1)
print(f"Deep Zoom levels: {dz.level_count}")
print(f"Total tiles: {dz.tile_count}")
# Generate DZI metadata
dzi_xml = dz.get_dzi('jpeg')
with open('slide.dzi', 'w') as f:
f.write(dzi_xml)
# Generate some tiles
for level in range(dz.level_count):
tiles_x, tiles_y = dz.level_tiles[level]
print(f"Level {level}: {tiles_x}x{tiles_y} tiles")
# Generate first tile of each level
if tiles_x > 0 and tiles_y > 0:
tile = dz.get_tile(level, (0, 0))
tile.save(f'tile_{level}_0_0.jpg')import os
import openslide
from openslide.deepzoom import DeepZoomGenerator
def generate_tiles(slide_path, output_dir, tile_format='jpeg'):
"""Generate complete Deep Zoom tile pyramid for a slide."""
with openslide.open_slide(slide_path) as slide:
# Create Deep Zoom generator
dz = DeepZoomGenerator(slide, tile_size=254, overlap=1)
# Create output directory
os.makedirs(output_dir, exist_ok=True)
# Save DZI metadata
dzi_path = os.path.join(output_dir, 'slide.dzi')
with open(dzi_path, 'w') as f:
f.write(dz.get_dzi(tile_format))
# Create tiles directory
tiles_dir = os.path.join(output_dir, 'slide_files')
os.makedirs(tiles_dir, exist_ok=True)
# Generate all tiles
for level in range(dz.level_count):
level_dir = os.path.join(tiles_dir, str(level))
os.makedirs(level_dir, exist_ok=True)
tiles_x, tiles_y = dz.level_tiles[level]
print(f"Generating level {level}: {tiles_x}x{tiles_y} tiles")
for col in range(tiles_x):
for row in range(tiles_y):
tile = dz.get_tile(level, (col, row))
tile_path = os.path.join(level_dir, f'{col}_{row}.{tile_format}')
if tile_format == 'jpeg':
tile.save(tile_path, 'JPEG', quality=90)
else:
tile.save(tile_path, 'PNG')
# Usage
generate_tiles('slide.svs', 'output_tiles', 'jpeg')import openslide
from openslide.deepzoom import DeepZoomGenerator
# Open a slide
with openslide.open_slide("slide.svs") as slide:
# Check if slide has bounds information
bounds_props = [
openslide.PROPERTY_NAME_BOUNDS_X,
openslide.PROPERTY_NAME_BOUNDS_Y,
openslide.PROPERTY_NAME_BOUNDS_WIDTH,
openslide.PROPERTY_NAME_BOUNDS_HEIGHT
]
has_bounds = all(prop in slide.properties for prop in bounds_props)
if has_bounds:
print("Slide has bounds information - using limited bounds")
# Create generator with bounds limiting
dz = DeepZoomGenerator(slide, limit_bounds=True)
# Show bounds information
bounds_x = int(slide.properties[openslide.PROPERTY_NAME_BOUNDS_X])
bounds_y = int(slide.properties[openslide.PROPERTY_NAME_BOUNDS_Y])
bounds_w = int(slide.properties[openslide.PROPERTY_NAME_BOUNDS_WIDTH])
bounds_h = int(slide.properties[openslide.PROPERTY_NAME_BOUNDS_HEIGHT])
print(f"Bounds: ({bounds_x}, {bounds_y}) {bounds_w}x{bounds_h}")
print(f"Original dimensions: {slide.dimensions}")
print(f"Deep Zoom dimensions: {dz.level_dimensions[-1]}")
else:
print("No bounds information - using full slide")
dz = DeepZoomGenerator(slide, limit_bounds=False)
# Generate some tiles
highest_level = dz.level_count - 1
tile = dz.get_tile(highest_level, (0, 0))
tile.save('bounds_tile.jpg')import openslide
from openslide.deepzoom import DeepZoomGenerator
with openslide.open_slide("slide.svs") as slide:
# Different configurations for different use cases
# High quality with larger tiles (good for detailed viewing)
dz_hq = DeepZoomGenerator(slide, tile_size=510, overlap=2)
# Fast loading with smaller tiles (good for overview)
dz_fast = DeepZoomGenerator(slide, tile_size=126, overlap=1)
# Standard configuration (tile_size + 2*overlap = 256, power of 2)
dz_standard = DeepZoomGenerator(slide, tile_size=254, overlap=1)
print("Configuration comparison:")
print(f"High quality: {dz_hq.tile_count} tiles")
print(f"Fast loading: {dz_fast.tile_count} tiles")
print(f"Standard: {dz_standard.tile_count} tiles")
# Generate sample tiles with different configurations
for name, dz in [('hq', dz_hq), ('fast', dz_fast), ('standard', dz_standard)]:
if dz.level_count > 0:
mid_level = dz.level_count // 2
tiles_x, tiles_y = dz.level_tiles[mid_level]
if tiles_x > 0 and tiles_y > 0:
tile = dz.get_tile(mid_level, (0, 0))
tile.save(f'sample_{name}.jpg')import json
import openslide
from openslide.deepzoom import DeepZoomGenerator
def create_viewer_config(slide_path, output_path):
"""Create configuration for OpenSeadragon web viewer."""
with openslide.open_slide(slide_path) as slide:
dz = DeepZoomGenerator(slide, tile_size=254, overlap=1)
# Extract slide information
config = {
'slide_info': {
'dimensions': slide.dimensions,
'level_count': slide.level_count,
'level_dimensions': slide.level_dimensions,
'properties': dict(slide.properties),
'associated_images': list(slide.associated_images.keys())
},
'deepzoom_info': {
'level_count': dz.level_count,
'level_dimensions': dz.level_dimensions,
'level_tiles': dz.level_tiles,
'tile_count': dz.tile_count,
'tile_size': 254,
'overlap': 1
},
'viewer_config': {
'tileSources': 'slide.dzi',
'prefixUrl': 'openseadragon/images/',
'showNavigator': True,
'showRotationControl': True,
'showHomeControl': True,
'showZoomControl': True,
'showFullPageControl': True
}
}
# Save configuration
with open(output_path, 'w') as f:
json.dump(config, f, indent=2)
return config
# Create viewer configuration
config = create_viewer_config('slide.svs', 'viewer_config.json')
print(f"Created viewer config for {config['slide_info']['dimensions']} slide")Install with Tessl CLI
npx tessl i tessl/pypi-openslide-python