Skip to content

[Feature] Performance Issue: Slow thumbnail generation for single-layer TIFF files in OpenSlideWSI #149

@XinhengLyu

Description

@XinhengLyu

Motivation

When processing large single-layer TIFF files, the current OpenSlideWSI.get_thumbnail() method is extremely slow. Performance testing shows OpenSlide takes ~10 seconds while a tifffile-based approach takes ~0.5 seconds (about 20x faster) for the same thumbnail generation.

Proposed Solution

Proposed Solution:
Implement a hybrid approach in OpenSlideWSI.get_thumbnail() that:

Uses tifffile library with smart downsampling for single-layer TIFF files
Falls back to OpenSlide's native method for other WSI formats (.svs, .ndpi, etc.)
Maintains full backward compatibility

def get_thumbnail(self, size: tuple[int, int]) -> Image.Image:
    """
    Generate a thumbnail of the WSI using optimized method based on file type.

    For single-layer TIFF files, uses tifffile for better performance.
    For other WSI formats, uses OpenSlide's native method.

    Parameters
    ----------
    size : tuple of int
        Desired (width, height) of the thumbnail.

    Returns
    -------
    PIL.Image.Image
        RGB thumbnail as a PIL Image.
    """
    # Use tifffile optimization for TIFF files
    if self.slide_path.lower().endswith(('.tiff', '.tif')):
        try:
            import tifffile

            with tifffile.TiffFile(self.slide_path) as tif:
                page = tif.pages[0]
                shape = page.shape
                h, w = shape[:2]

                # Calculate aspect-preserving thumbnail size
                aspect_ratio = w / h
                target_w, target_h = size

                if aspect_ratio > (target_w / target_h):
                    final_w = target_w
                    final_h = round(target_w / aspect_ratio)
                else:
                    final_h = target_h
                    final_w = round(target_h * aspect_ratio)

                # Calculate optimal downsampling steps
                step_h = max(1, h // (final_h * 2))
                step_w = max(1, w // (final_w * 2))

                # Read downsampled data
                if len(shape) == 2:  # Grayscale
                    data = page.asarray()[::step_h, ::step_w]
                    img = Image.fromarray(data, mode='L').convert('RGB')
                else:  # Color
                    data = page.asarray()[::step_h, ::step_w, :]
                    img = Image.fromarray(data[:, :, :3], mode='RGB')

                # Resize to exact target dimensions
                return img.resize((final_w, final_h), Image.Resampling.LANCZOS)

        except ImportError:
            pass  # tifffile not available, fall back to OpenSlide
        except Exception:
            pass  # tifffile failed, fall back to OpenSlide

    # Use OpenSlide's native method for other formats (.svs, .ndpi, etc.)
    return self.img.get_thumbnail(size).convert('RGB')

This would significantly improve user experience for single-layer TIFF processing while preserving existing functionality for multi-layer WSI workflow

Who does this help?

None

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions