Claude Skill

alterlab-histolab

Extract and preprocess tiles from whole-slide images (WSI) with histolab — OpenSlide-backed slide loading, tissue detection and masks, Random/Grid/Score tile extraction, and image/morphological filters for H&E preprocessing. Use when the user needs lightweight WSI slide preproces

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download alterlab-ieu-alterlab-academic-skills-skills_bioinformatics_alterlab-histolab-e4836c0.zip · 27 KB
Part of alterlab-ieu/alterlab-academic-skills — 94 skills

Install

skills CLI npx skills add https://github.com/AlterLab-IEU/AlterLab-Academic-Skills/tree/main/skills/bioinformatics/alterlab-histolab
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install alterlab-ieu-alterlab-academic-skills@llmmart
Git git clone https://github.com/AlterLab-IEU/AlterLab-Academic-Skills.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole alterlab-ieu/alterlab-academic-skills collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

Histolab

Overview

Histolab is a Python library for processing whole slide images (WSI) in digital pathology. It automates tissue detection, extracts informative tiles from gigapixel images, and prepares datasets for deep learning pipelines. The library handles multiple WSI formats, implements sophisticated tissue segmentation, and provides flexible tile extraction strategies.

When to Use This Skill

Use histolab for lightweight WSI tile pipelines: tissue detection, building tile datasets for ML training, H&E stain handling, and quick tile-based analysis of histopathology slides. For advanced spatial proteomics, multiplexed imaging, or full deep-learning pathology pipelines, use alterlab-pathml instead.

Does NOT Trigger

Scenario Use Instead
End-to-end computational pathology, nucleus segmentation, multiplexed imaging (CODEX/Vectra) alterlab-pathml
Training the downstream classifier on the extracted tiles alterlab-pytorch-lightning
DICOM whole-slide or radiology images rather than SVS/NDPI alterlab-pydicom, alterlab-imaging-data-commons
Spatial transcriptomics on tissue sections alterlab-squidpy-spatial
Managing/versioning the slide collection itself alterlab-omero, alterlab-lamindb

Installation

# histolab 0.7.0 needs Python >= 3.8, < 3.12 and numpy 1.x — give it its own env
uv venv --python 3.11 .venv-histolab
uv pip install --python .venv-histolab "histolab==0.7.0" openslide-bin

Two constraints that bite in a 2026 toolchain:

  1. Pinned scientific stack. 0.7.0 (Feb 2024, still the latest release) requires numpy <= 1.24.4, scipy < 1.10.1, scikit-image < 0.19.4 and openslide-python 1.3.1. It cannot share an environment with a numpy 2.x / Python 3.12+ project — resolve this with a separate venv rather than by loosening pins.
  2. OpenSlide is a C library, not bundled with the wheel. The simplest fix is the openslide-bin wheel (prebuilt binaries); otherwise brew install openslide on macOS or apt install libopenslide0 on Debian/Ubuntu. Without it, import histolab.slide fails with Couldn't locate OpenSlide dylib.

The examples below target histolab 0.7.0; the API differs in older releases.

Core Workflow

  1. Load the slide with Slide(path, processed_path=...) and inspect dimensions/levels.
  2. Detect tissue with a mask (TissueMask or BiggestTissueBoxMask).
  3. Preview tile locations with tiler.locate_tiles(slide) before committing.
  4. Extract tiles with one of three tilers (Random/Grid/Score).

Minimal example:

from histolab.slide import Slide
from histolab.tiler import RandomTiler

slide = Slide("slide.svs", processed_path="output/")
# n_tiles, level, seed are CONSTRUCTOR args — not args to locate_tiles/extract.
tiler = RandomTiler(tile_size=(512, 512), n_tiles=100, level=0, seed=42)
tiler.locate_tiles(slide)   # preview locations on the thumbnail first
tiler.extract(slide)        # writes PNGs into processed_path

API gotcha (histolab 0.7.0): locate_tiles() and extract() take only slide, an optional extraction_mask, and logging/styling kwargs — they do not accept n_tiles. Set n_tiles (and seed, level, tile_size, check_tissue, tissue_percent) on the tiler constructor. The extraction_mask is passed to extract()/locate_tiles(), never to the constructor.

Full copy-pasteable pipelines (quick start, 5 end-to-end workflows, and per-capability examples) live in references/workflows.md.

Core Capabilities

1. Slide Management

Load, inspect, and work with WSI files (SVS, TIFF, NDPI, etc.): access metadata (dimensions, magnification, properties), generate thumbnails, and work with pyramidal/multi-level structures. Key class: Slide.

See references/slide_management.md for slide initialization, built-in sample datasets (prostate_tissue, ovarian_tissue, breast_tissue, heart_tissue, aorta_tissue, plus pen-marked and IHC samples), pyramid levels, and multi-slide processing.

2. Tissue Detection and Masks

Automatically identify tissue regions and filter background/artifacts. Key classes: TissueMask (all tissue regions), BiggestTissueBoxMask (bounding box of largest region — the default), and BinaryMask (base class for custom masks).

Choosing a mask:

  • TissueMask: multiple tissue sections, comprehensive analysis
  • BiggestTissueBoxMask: single main section, exclude artifacts (default)
  • Custom BinaryMask: specific ROI, exclude annotations, custom segmentation

See references/tissue_masks.md for how detection filters work, visualizing masks with locate_mask(), and custom rectangular / annotation-exclusion masks.

3. Tile Extraction

Extract smaller regions from large WSI using one of three strategies:

  • RandomTiler — fixed number of randomly positioned tiles. Best for sampling diverse regions, exploration, training data. Key params: n_tiles, seed.
  • GridTiler — systematic grid across tissue. Best for complete coverage, spatial analysis, reconstruction. Key param: pixel_overlap.
  • ScoreTiler — top-ranked tiles by scoring function. Best for informative regions, quality-driven selection. Key param: scorer (NucleiScorer, CellularityScorer, custom).

Common parameters: tile_size, level (0 = highest res), check_tissue, tissue_percent (default 80%), extraction_mask. Always preview with locate_tiles() before extracting.

See references/tile_extraction.md for scorers, reporting, and advanced (multi-level, hierarchical) extraction patterns.

4. Filters and Preprocessing

Apply image-processing filters for tissue detection, QC, and preprocessing:

  • Image filtersRgbToGrayscale, RgbToHsv, RgbToHed, OtsuThreshold, Invert, StretchContrast, HistogramEqualization, Lambda.
  • Morphological filtersBinaryDilation, BinaryErosion, BinaryOpening, BinaryClosing, RemoveSmallObjects, RemoveSmallHoles.
  • CompositionCompose (in histolab.filters.image_filters) chains filters into pipelines. Pass custom filters to a mask as positional varargs: TissueMask(RgbToGrayscale(), OtsuThreshold(), ...).

See references/filters_preprocessing.md for filter chaining, common pipelines (tissue detection, pen removal, nuclei enhancement), and QC filters.

5. Visualization

Display slides, masks, tile locations, and extraction quality: thumbnails, mask overlays via locate_mask(), tile-location previews via locate_tiles(), tile mosaics, and score distributions.

See references/visualization.md for mosaics, quality-assessment plots, multi-slide comparison, and exporting high-resolution figures / PDF reports.

Reference Index

  • references/workflows.md — quick start, per-capability examples, and 5 end-to-end worked workflows (exploratory, grid, score-driven, multi-slide, custom tissue detection).
  • references/slide_management.md — loading/inspecting slides, sample datasets, pyramid levels, multi-slide processing.
  • references/tissue_masks.mdTissueMask/BiggestTissueBoxMask/BinaryMask, custom masks, mask visualization and integration.
  • references/tile_extraction.md — Random/Grid/Score tiler comparison, scorers, CSV reporting, advanced extraction patterns.
  • references/filters_preprocessing.md — image + morphological filters, filter composition, preprocessing pipelines, QC filters.
  • references/visualization.md — thumbnails, mask/tile previews, mosaics, quality plots, figure export.
  • references/best_practices.md — best practices, common use cases, and troubleshooting (no tiles, background tiles, slow extraction, artifacts).

Load the specific reference file you need for detailed implementation guidance, troubleshooting, or advanced features.

Part of the AlterLab Academic Skills suite.

Files (alterlab-academic-skills)
  • evals
    • evals.json 4.8 KB
      {
        "skill": "alterlab-histolab",
        "evals": [
          {
            "id": "random-tiles-from-svs",
            "prompt": "I have a whole-slide image slide.svs and I want to extract 100 random 512x512 tiles at full resolution, but only from actual tissue (skip background), and I need it reproducible. Preview where they'll land first.",
            "expected_output": "Invokes alterlab-histolab: loads Slide(...), configures RandomTiler(tile_size=(512,512), n_tiles=100, level=0, seed=..., check_tissue=True, tissue_percent=...), calls locate_tiles to preview on the thumbnail, then tiler.extract(slide).",
            "assertions": [
              { "type": "should_trigger", "value": true },
              { "type": "output_contains", "value": "RandomTiler" },
              { "type": "behavior", "value": "Uses RandomTiler with a seed and check_tissue, and previews with locate_tiles before extracting." }
            ]
          },
          {
            "id": "grid-tiles-full-coverage",
            "prompt": "I need complete, non-overlapping coverage of all the tissue on my slide for downstream whole-slide analysis - every tile in a grid, filtered to tissue regions, ideally at a lower pyramid level so it's faster.",
            "expected_output": "Invokes alterlab-histolab: uses a TissueMask for all sections and a GridTiler(tile_size=..., level=1, pixel_overlap=0, check_tissue=True), previews the grid with locate_tiles, then extracts with the extraction_mask. Comprehensive grid-coverage strategy.",
            "assertions": [
              { "type": "should_trigger", "value": true },
              { "type": "output_contains", "value": "GridTiler" },
              { "type": "behavior", "value": "Chooses GridTiler with pixel_overlap=0 for full coverage and a TissueMask, extracting at a lower level for speed." }
            ]
          },
          {
            "id": "score-tiler-nuclei-rich",
            "prompt": "For my ML training set I only want the most informative, cell-dense regions of each slide - the top 50 tiles ranked by nuclei content - and a CSV of their scores so I can inspect the distribution.",
            "expected_output": "Invokes alterlab-histolab: uses ScoreTiler with a NucleiScorer, n_tiles=50, and extract(slide, report_path='...csv') to write per-tile scores, suggesting inspection of the score distribution. Quality-driven tile selection.",
            "assertions": [
              { "type": "should_trigger", "value": true },
              { "type": "output_contains", "value": "ScoreTiler" },
              { "type": "behavior", "value": "Uses ScoreTiler with NucleiScorer and a report_path CSV to rank tiles by nuclei density." }
            ]
          },
          {
            "id": "custom-tissue-mask-artifacts",
            "prompt": "My slides have a lot of pen marks and small debris that keep getting picked up as tissue. How can I build a stricter tissue mask that removes those artifacts before I extract tiles?",
            "expected_output": "Invokes alterlab-histolab: builds a stricter tissue-detection filter chain (RgbToGrayscale, OtsuThreshold, BinaryDilation, RemoveSmallHoles, RemoveSmallObjects with larger area thresholds) and passes those filters to TissueMask as positional varargs (TissueMask(RgbToGrayscale(), OtsuThreshold(), ...)), visualizes with locate_mask, then extracts passing that mask as extract(slide, extraction_mask=...).",
            "assertions": [
              { "type": "should_trigger", "value": true },
              { "type": "output_contains", "value": "TissueMask" },
              { "type": "behavior", "value": "Composes morphological filters (RemoveSmallObjects/Holes) into a custom TissueMask to drop artifacts, and previews with locate_mask." }
            ]
          },
          {
            "id": "near-miss-pathml",
            "prompt": "I have a multiplexed immunofluorescence whole-slide image with a dozen protein channels and I want to run a full deep-learning pathology pipeline - cell segmentation, marker quantification, and spatial proteomics analysis.",
            "expected_output": "Should NOT trigger alterlab-histolab. Multiplexed imaging, cell segmentation, and full deep-learning / spatial-proteomics pathology pipelines are alterlab-pathml territory. histolab is the lightweight tile-extraction/tissue-detection skill and defers advanced multiplexed and DL pipelines to pathml.",
            "assertions": [
              { "type": "should_not_trigger", "value": true },
              { "type": "output_contains", "value": "pathml" }
            ]
          },
          {
            "id": "near-miss-scanpy",
            "prompt": "I have an .h5ad of single-cell RNA-seq counts. Run QC filtering, normalize, cluster with Leiden, and find marker genes per cluster.",
            "expected_output": "Should NOT trigger alterlab-histolab. This is single-cell RNA-seq analysis (QC, normalization, Leiden clustering, markers) which is alterlab-scanpy territory and has nothing to do with whole-slide histopathology images. histolab handles WSI tiles, not transcriptomic matrices.",
            "assertions": [
              { "type": "should_not_trigger", "value": true },
              { "type": "output_contains", "value": "scanpy" }
            ]
          }
        ]
      }
      
  • references
    • best_practices.md 3.7 KB
      # Histolab Best Practices, Use Cases & Troubleshooting
      
      ## Best Practices
      
      ### Slide Loading and Inspection
      1. Always inspect slide properties before processing
      2. Save thumbnails for quick visual review
      3. Check pyramid levels and dimensions
      4. Verify tissue is present using thumbnails
      
      ### Tissue Detection
      1. Preview masks with `locate_mask()` before extraction
      2. Use `TissueMask` for multiple sections, `BiggestTissueBoxMask` for single sections
      3. Customize filters for specific stains (H&E vs IHC)
      4. Handle pen annotations with custom masks
      5. Test masks on diverse slides
      
      ### Tile Extraction
      1. **Always preview with `locate_tiles()` before extracting**
      2. Choose appropriate tiler:
         - RandomTiler: Sampling and exploration
         - GridTiler: Complete coverage
         - ScoreTiler: Quality-driven selection
      3. Set appropriate `tissue_percent` threshold (70-90% typical)
      4. Use seeds for reproducibility in RandomTiler
      5. Extract at appropriate pyramid level for analysis resolution
      6. Enable logging for large datasets
      
      ### Performance
      1. Extract at lower levels (1, 2) for faster processing
      2. Use `BiggestTissueBoxMask` over `TissueMask` when appropriate
      3. Adjust `tissue_percent` to reduce invalid tile attempts
      4. Limit `n_tiles` for initial exploration
      5. Use `pixel_overlap=0` for non-overlapping grids
      
      ### Quality Control
      1. Validate tile quality (check for blur, artifacts, focus)
      2. Review score distributions for ScoreTiler
      3. Inspect top and bottom scoring tiles
      4. Monitor tissue coverage statistics
      5. Filter extracted tiles by additional quality metrics if needed
      
      ## Common Use Cases
      
      ### Training Deep Learning Models
      - Extract balanced datasets using RandomTiler across multiple slides
      - Use ScoreTiler with NucleiScorer to focus on cell-rich regions
      - Extract at consistent resolution (level 0 or level 1)
      - Generate CSV reports for tracking tile metadata
      
      ### Whole Slide Analysis
      - Use GridTiler for complete tissue coverage
      - Extract at multiple pyramid levels for hierarchical analysis
      - Maintain spatial relationships with grid positions
      - Use `pixel_overlap` for sliding window approaches
      
      ### Tissue Characterization
      - Sample diverse regions with RandomTiler
      - Quantify tissue coverage with masks
      - Extract stain-specific information with HED decomposition
      - Compare tissue patterns across slides
      
      ### Quality Assessment
      - Identify optimal focus regions with ScoreTiler
      - Detect artifacts using custom masks and filters
      - Assess staining quality across slide collection
      - Flag problematic slides for manual review
      
      ### Dataset Curation
      - Use ScoreTiler to prioritize informative tiles
      - Filter tiles by tissue percentage
      - Generate reports with tile scores and metadata
      - Create stratified datasets across slides and tissue types
      
      ## Troubleshooting
      
      ### No tiles extracted
      - Lower `tissue_percent` threshold
      - Verify slide contains tissue (check thumbnail)
      - Ensure extraction_mask captures tissue regions
      - Check tile_size is appropriate for slide resolution
      
      ### Many background tiles
      - Enable `check_tissue=True`
      - Increase `tissue_percent` threshold
      - Use appropriate mask (TissueMask vs BiggestTissueBoxMask)
      - Customize mask filters to better detect tissue
      
      ### Extraction very slow
      - Extract at lower pyramid level (level=1 or 2)
      - Reduce `n_tiles` for RandomTiler/ScoreTiler
      - Use RandomTiler instead of GridTiler for sampling
      - Use BiggestTissueBoxMask instead of TissueMask
      
      ### Tiles have artifacts
      - Implement custom annotation-exclusion masks
      - Adjust filter parameters for artifact removal
      - Increase small object removal threshold
      - Apply post-extraction quality filtering
      
      ### Inconsistent results across slides
      - Use same seed for RandomTiler
      - Normalize staining with preprocessing filters
      - Adjust `tissue_percent` per staining quality
      - Implement slide-specific mask customization
      
    • filters_preprocessing.md 11.9 KB
      # Filters and Preprocessing
      
      ## Overview
      
      Histolab provides a comprehensive set of filters for preprocessing whole slide images and tiles. Filters can be applied to images for visualization, quality control, tissue detection, and artifact removal. They are composable and can be chained together to create sophisticated preprocessing pipelines.
      
      ## Filter Categories
      
      ### Image Filters
      Color space conversions, thresholding, and intensity adjustments
      
      ### Morphological Filters
      Structural operations like dilation, erosion, opening, and closing
      
      ### Composition Filters
      Utilities for combining multiple filters
      
      ## Image Filters
      
      ### RgbToGrayscale
      
      Convert RGB images to grayscale.
      
      ```python
      from histolab.filters.image_filters import RgbToGrayscale
      
      gray_filter = RgbToGrayscale()
      gray_image = gray_filter(rgb_image)
      ```
      
      **Use cases:**
      - Preprocessing for intensity-based operations
      - Simplifying color complexity
      - Input for morphological operations
      
      ### RgbToHsv
      
      Convert RGB to HSV (Hue, Saturation, Value) color space.
      
      ```python
      from histolab.filters.image_filters import RgbToHsv
      
      hsv_filter = RgbToHsv()
      hsv_image = hsv_filter(rgb_image)
      ```
      
      **Use cases:**
      - Color-based tissue segmentation
      - Detecting pen markings by hue
      - Separating chromatic from achromatic content
      
      ### RgbToHed
      
      Convert RGB to HED (Hematoxylin-Eosin-DAB) color space for stain deconvolution.
      
      ```python
      from histolab.filters.image_filters import RgbToHed
      
      hed_filter = RgbToHed()
      hed_image = hed_filter(rgb_image)
      ```
      
      **Use cases:**
      - Separating H&E stain components
      - Analyzing nuclear (hematoxylin) vs. cytoplasmic (eosin) staining
      - Quantifying stain intensity
      
      ### OtsuThreshold
      
      Apply Otsu's automatic thresholding method to create binary images.
      
      ```python
      from histolab.filters.image_filters import OtsuThreshold
      
      otsu_filter = OtsuThreshold()
      binary_image = otsu_filter(grayscale_image)
      ```
      
      **How it works:**
      - Automatically determines optimal threshold
      - Separates foreground from background
      - Minimizes intra-class variance
      
      **Use cases:**
      - Tissue detection
      - Nuclei segmentation
      - Binary mask creation
      
      ### Invert
      
      Invert image intensity values.
      
      ```python
      from histolab.filters.image_filters import Invert
      
      invert_filter = Invert()
      inverted_image = invert_filter(image)
      ```
      
      **Use cases:**
      - Preprocessing for certain segmentation algorithms
      - Visualization adjustments
      
      ### StretchContrast
      
      Enhance image contrast by stretching intensity range.
      
      ```python
      from histolab.filters.image_filters import StretchContrast
      
      contrast_filter = StretchContrast()
      enhanced_image = contrast_filter(image)
      ```
      
      **Use cases:**
      - Improving visibility of low-contrast features
      - Preprocessing for visualization
      - Enhancing faint structures
      
      ### HistogramEqualization
      
      Equalize image histogram for contrast enhancement.
      
      ```python
      from histolab.filters.image_filters import HistogramEqualization
      
      hist_eq_filter = HistogramEqualization()
      equalized_image = hist_eq_filter(grayscale_image)
      ```
      
      **Use cases:**
      - Standardizing image contrast
      - Revealing hidden details
      - Preprocessing for feature extraction
      
      ## Morphological Filters
      
      ### BinaryDilation
      
      Expand white regions in binary images.
      
      ```python
      from histolab.filters.morphological_filters import BinaryDilation
      
      dilation_filter = BinaryDilation(disk_size=5)
      dilated_image = dilation_filter(binary_image)
      ```
      
      **Parameters:**
      - `disk_size`: Size of structuring element (default: 5)
      
      **Use cases:**
      - Connecting nearby tissue regions
      - Filling small gaps
      - Expanding tissue masks
      
      ### BinaryErosion
      
      Shrink white regions in binary images.
      
      ```python
      from histolab.filters.morphological_filters import BinaryErosion
      
      erosion_filter = BinaryErosion(disk_size=5)
      eroded_image = erosion_filter(binary_image)
      ```
      
      **Use cases:**
      - Removing small protrusions
      - Separating connected objects
      - Shrinking tissue boundaries
      
      ### BinaryOpening
      
      Erosion followed by dilation (removes small objects).
      
      ```python
      from histolab.filters.morphological_filters import BinaryOpening
      
      opening_filter = BinaryOpening(disk_size=3)
      opened_image = opening_filter(binary_image)
      ```
      
      **Use cases:**
      - Removing small artifacts
      - Smoothing object boundaries
      - Noise reduction
      
      ### BinaryClosing
      
      Dilation followed by erosion (fills small holes).
      
      ```python
      from histolab.filters.morphological_filters import BinaryClosing
      
      closing_filter = BinaryClosing(disk_size=5)
      closed_image = closing_filter(binary_image)
      ```
      
      **Use cases:**
      - Filling small holes in tissue regions
      - Connecting nearby objects
      - Smoothing internal boundaries
      
      ### RemoveSmallObjects
      
      Remove connected components smaller than a threshold.
      
      ```python
      from histolab.filters.morphological_filters import RemoveSmallObjects
      
      remove_small_filter = RemoveSmallObjects(
          area_threshold=500  # Minimum area in pixels
      )
      cleaned_image = remove_small_filter(binary_image)
      ```
      
      **Use cases:**
      - Removing dust and artifacts
      - Filtering noise
      - Cleaning tissue masks
      
      ### RemoveSmallHoles
      
      Fill holes smaller than a threshold.
      
      ```python
      from histolab.filters.morphological_filters import RemoveSmallHoles
      
      fill_holes_filter = RemoveSmallHoles(
          area_threshold=1000  # Maximum hole size to fill
      )
      filled_image = fill_holes_filter(binary_image)
      ```
      
      **Use cases:**
      - Filling small gaps in tissue
      - Creating continuous tissue regions
      - Removing internal artifacts
      
      ## Filter Composition
      
      ### Chaining Filters
      
      Combine multiple filters in sequence:
      
      ```python
      from histolab.filters.image_filters import RgbToGrayscale, OtsuThreshold, Compose
      from histolab.filters.morphological_filters import (
          BinaryDilation, RemoveSmallObjects, RemoveSmallHoles
      )
      
      # Create filter pipeline (Compose lives in image_filters and takes a list)
      tissue_detection_pipeline = Compose([
          RgbToGrayscale(),
          OtsuThreshold(),
          BinaryDilation(disk_size=5),
          RemoveSmallHoles(area_threshold=1000),
          RemoveSmallObjects(min_size=500)
      ])
      
      # Apply pipeline
      result = tissue_detection_pipeline(rgb_image)
      ```
      
      ### Lambda Filters
      
      Create custom filters inline:
      
      ```python
      from histolab.filters.image_filters import Lambda
      import numpy as np
      
      # Custom brightness adjustment
      brightness_filter = Lambda(lambda img: np.clip(img * 1.2, 0, 255).astype(np.uint8))
      
      # Custom color channel extraction
      red_channel_filter = Lambda(lambda img: img[:, :, 0])
      ```
      
      ## Common Preprocessing Pipelines
      
      ### Standard Tissue Detection
      
      ```python
      from histolab.filters.image_filters import RgbToGrayscale, OtsuThreshold, Compose
      from histolab.filters.morphological_filters import (
          BinaryDilation, RemoveSmallHoles, RemoveSmallObjects
      )
      
      tissue_detection = Compose([
          RgbToGrayscale(),
          OtsuThreshold(),
          BinaryDilation(disk_size=5),
          RemoveSmallHoles(area_threshold=1000),
          RemoveSmallObjects(min_size=500)
      ])
      ```
      
      ### Pen Mark Removal
      
      ```python
      from histolab.filters.image_filters import RgbToHsv, Lambda, Compose
      import numpy as np
      
      def remove_pen_marks(hsv_image):
          """Remove blue/green pen markings."""
          h, s, v = hsv_image[:, :, 0], hsv_image[:, :, 1], hsv_image[:, :, 2]
          # Mask for blue/green hues (common pen colors)
          pen_mask = ((h > 0.45) & (h < 0.7) & (s > 0.3))
          # Set pen regions to white
          hsv_image[pen_mask] = [0, 0, 1]
          return hsv_image
      
      pen_removal = Compose([
          RgbToHsv(),
          Lambda(remove_pen_marks)
      ])
      ```
      
      ### Nuclei Enhancement
      
      ```python
      from histolab.filters.image_filters import (
          RgbToHed, HistogramEqualization, Lambda, Compose
      )
      
      nuclei_enhancement = Compose([
          RgbToHed(),
          Lambda(lambda hed: hed[:, :, 0]),  # Extract hematoxylin channel
          HistogramEqualization()
      ])
      ```
      
      ### Contrast Normalization
      
      ```python
      from histolab.filters.image_filters import (
          RgbToGrayscale, StretchContrast, HistogramEqualization, Compose
      )
      
      contrast_normalization = Compose([
          RgbToGrayscale(),
          StretchContrast(),
          HistogramEqualization()
      ])
      ```
      
      ## Applying Filters to Tiles
      
      Filters can be applied to individual tiles:
      
      ```python
      from histolab.tile import Tile
      from histolab.filters.image_filters import RgbToGrayscale
      
      # Load or extract tile
      tile = Tile(image=pil_image, coords=(x, y))
      
      # Apply filter
      gray_filter = RgbToGrayscale()
      filtered_tile = tile.apply_filters(gray_filter)
      
      # Chain multiple filters
      from histolab.filters.image_filters import StretchContrast, Compose
      
      filter_chain = Compose([
          RgbToGrayscale(),
          StretchContrast()
      ])
      processed_tile = tile.apply_filters(filter_chain)
      ```
      
      ## Custom Mask Filters
      
      Integrate custom filters with tissue masks:
      
      ```python
      from histolab.masks import TissueMask
      from histolab.filters.image_filters import RgbToGrayscale, OtsuThreshold
      from histolab.filters.morphological_filters import (
          BinaryDilation, RemoveSmallObjects
      )
      
      # Custom aggressive tissue detection. TissueMask takes the individual filters as
      # positional varargs (TissueMask(*filters)) — not a Compose object or filters=.
      custom_mask = TissueMask(
          RgbToGrayscale(),
          OtsuThreshold(),
          BinaryDilation(disk_size=10),       # Larger dilation
          RemoveSmallObjects(min_size=5000),  # Remove only large artifacts
      )
      ```
      
      ## Stain Normalization
      
      histolab 0.7.0 ships built-in stain normalizers in `histolab.stain_normalizer`:
      `MacenkoStainNormalizer` and `ReinhardStainNormalizer`. Both follow a
      fit-on-target / transform-on-source pattern (PIL images in, PIL image out).
      
      ```python
      from histolab.stain_normalizer import MacenkoStainNormalizer
      # from histolab.stain_normalizer import ReinhardStainNormalizer  # alternative
      
      normalizer = MacenkoStainNormalizer()
      
      # Fit to a reference slide/tile whose staining you want to match
      normalizer.fit(reference_tile.image)  # reference_tile.image is a PIL.Image
      
      # Normalize a new tile to that reference
      normalized = normalizer.transform(tile.image)  # returns a PIL.Image
      ```
      
      Use this instead of hand-rolling HED-channel rescaling — the built-in methods
      estimate the stain matrix (Macenko) or LAB color statistics (Reinhard) properly.
      
      ## Best Practices
      
      1. **Preview filters**: Visualize filter outputs on thumbnails before applying to tiles
      2. **Chain efficiently**: Order filters logically (e.g., color conversion before thresholding)
      3. **Tune parameters**: Adjust thresholds and structuring element sizes for specific tissues
      4. **Use composition**: Build reusable filter pipelines with `Compose`
      5. **Consider performance**: Complex filter chains increase processing time
      6. **Validate on diverse slides**: Test filters across different scanners, stains, and tissue types
      7. **Document custom filters**: Clearly describe purpose and parameters of custom pipelines
      
      ## Quality Control Filters
      
      ### Blur Detection
      
      ```python
      from histolab.filters.image_filters import Lambda
      import cv2
      import numpy as np
      
      def laplacian_blur_score(gray_image):
          """Calculate Laplacian variance (blur metric)."""
          return cv2.Laplacian(np.array(gray_image), cv2.CV_64F).var()
      
      blur_detector = Lambda(lambda img: laplacian_blur_score(
          RgbToGrayscale()(img)
      ))
      ```
      
      ### Tissue Coverage
      
      ```python
      from histolab.filters.image_filters import RgbToGrayscale, OtsuThreshold, Compose
      
      def tissue_coverage(image):
          """Calculate percentage of tissue in image."""
          tissue_mask = Compose([
              RgbToGrayscale(),
              OtsuThreshold()
          ])(image)
          return tissue_mask.sum() / tissue_mask.size * 100
      
      coverage_filter = Lambda(tissue_coverage)
      ```
      
      ## Troubleshooting
      
      ### Issue: Tissue detection misses valid tissue
      **Solutions:**
      - Reduce `area_threshold` in `RemoveSmallObjects`
      - Decrease erosion/opening disk size
      - Detect tissue in HSV/HED space (e.g. saturation channel) before thresholding
      
      ### Issue: Too many artifacts included
      **Solutions:**
      - Increase `area_threshold` in `RemoveSmallObjects`
      - Add opening/closing operations
      - Use custom color-based filtering for specific artifacts
      
      ### Issue: Tissue boundaries too rough
      **Solutions:**
      - Add `BinaryClosing` or `BinaryOpening` for smoothing
      - Adjust disk_size for morphological operations
      
      ### Issue: Variable staining quality
      **Solutions:**
      - Apply histogram equalization or contrast stretching
      - Normalize staining with `MacenkoStainNormalizer` / `ReinhardStainNormalizer`
      
    • slide_management.md 5.3 KB
      # Slide Management
      
      ## Overview
      
      The `Slide` class is the primary interface for working with whole slide images (WSI) in histolab. It provides methods to load, inspect, and process large histopathology images stored in various formats.
      
      ## Initialization
      
      ```python
      from histolab.slide import Slide
      
      # Initialize a slide with a WSI file and output directory.
      # The first positional/keyword arg is `path` (not `slide_path`).
      slide = Slide(path="path/to/slide.svs",
                    processed_path="path/to/processed/output")
      ```
      
      **Parameters:**
      - `path`: Path to the whole slide image file (supports multiple formats: SVS, TIFF, NDPI, etc.)
      - `processed_path`: Directory where processed outputs (tiles, thumbnails, etc.) will be saved (required — cannot be `None`)
      - `use_largeimage` (default `False`): use the `large_image` backend instead of OpenSlide, enabling exact microns-per-pixel tile fetching
      
      ## Loading Sample Data
      
      Histolab provides built-in sample datasets from TCGA for testing and demonstration:
      
      ```python
      from histolab.data import prostate_tissue, ovarian_tissue, breast_tissue, heart_tissue
      
      # Each loader returns (OpenSlide object, path); pass the path to Slide.
      prostate_svs, prostate_path = prostate_tissue()
      slide = Slide(prostate_path, processed_path="output/")
      ```
      
      Available sample datasets (histolab 0.7.0):
      - `prostate_tissue()`, `ovarian_tissue()`, `breast_tissue()`, `heart_tissue()`,
        `aorta_tissue()`: H&E tissue samples
      - `breast_tissue_diagnostic_green_pen()` / `_red_pen()` / `_black_pen()`: slides
        with pen-mark artifacts (handy for testing artifact-removal masks)
      - `ihc_breast()`, `ihc_kidney()`: IHC-stained samples
      - `cmu_small_region()`: tiny region for quick smoke tests
      
      > There is no `kidney_tissue()`; the kidney sample is `ihc_kidney()`.
      
      ## Key Properties
      
      ### Slide Dimensions
      ```python
      # Get slide dimensions at level 0 (highest resolution)
      width, height = slide.dimensions
      
      # level_dimensions is a METHOD that takes a level and returns (width, height)
      w1, h1 = slide.level_dimensions(level=1)
      ```
      
      ### Magnification Information
      ```python
      # Microns per pixel at level 0 (resolution, not objective magnification)
      mpp = slide.base_mpp
      
      # `levels` is a LIST of available pyramid levels (e.g. [0, 1, 2]), not a count.
      available_levels = slide.levels
      num_levels = len(slide.levels)
      ```
      
      ### Slide Properties
      ```python
      # Access OpenSlide properties dictionary
      properties = slide.properties
      
      # Common properties include:
      # - slide.properties['openslide.objective-power']: Objective power
      # - slide.properties['openslide.mpp-x']: Microns per pixel in X
      # - slide.properties['openslide.mpp-y']: Microns per pixel in Y
      # - slide.properties['openslide.vendor']: Scanner vendor
      ```
      
      ## Thumbnail Generation
      
      ```python
      # `thumbnail` is a property returning a PIL.Image
      thumbnail = slide.thumbnail
      
      # There is no save_thumbnail() method — save the PIL image directly
      slide.thumbnail.save("output/thumbnail.png")
      
      # scaled_image(scale_factor) is a method returning a downscaled PIL.Image
      scaled = slide.scaled_image(scale_factor=32)
      ```
      
      ## Slide Visualization
      
      ```python
      # Display slide thumbnail with matplotlib
      import matplotlib.pyplot as plt
      
      plt.figure(figsize=(10, 10))
      plt.imshow(slide.thumbnail)
      plt.title(f"Slide: {slide.name}")
      plt.axis('off')
      plt.show()
      ```
      
      ## Extracting a Single Tile
      
      ```python
      from histolab.types import CoordinatePair
      
      # Extract one tile at given level-0 coordinates. The method is extract_tile()
      # (there is no extract_region). coords is a CoordinatePair(x_ul, y_ul, x_br, y_br).
      tile = slide.extract_tile(
          coords=CoordinatePair(x_ul=0, y_ul=0, x_br=512, y_br=512),
          tile_size=(512, 512),
          level=0,
      )
      ```
      
      ## Working with Pyramid Levels
      
      WSI files use a pyramidal structure with multiple resolution levels:
      - Level 0: Highest resolution (native scan resolution)
      - Level 1+: Progressively lower resolutions for faster access
      
      ```python
      # `levels` is a list, so iterate it directly (don't wrap it in range()).
      for level in slide.levels:
          dims = slide.level_dimensions(level=level)  # method call, not subscript
          print(f"Level {level}: {dims}")
      ```
      
      ## Slide Name
      
      ```python
      # Get slide filename without extension
      slide_name = slide.name
      ```
      
      ## Best Practices
      
      1. **Always specify processed_path**: Organize outputs in dedicated directories
      2. **Check dimensions before processing**: Large slides can exceed memory limits
      3. **Use appropriate pyramid levels**: Extract tiles at levels matching your analysis resolution
      4. **Preview with thumbnails**: Use thumbnails for quick visualization before heavy processing
      5. **Monitor memory usage**: Level 0 operations on large slides require significant RAM
      
      ## Common Workflows
      
      ### Slide Inspection Workflow
      ```python
      from histolab.slide import Slide
      
      # Load slide
      slide = Slide("slide.svs", processed_path="output/")
      
      # Inspect properties
      print(f"Dimensions: {slide.dimensions}")
      print(f"Levels: {slide.levels}")
      print(f"Magnification: {slide.properties.get('openslide.objective-power', 'N/A')}")
      
      # Save thumbnail for review
      slide.thumbnail.save("output/thumbnail.png")
      ```
      
      ### Multi-Slide Processing
      ```python
      import os
      from pathlib import Path
      
      slide_dir = Path("slides/")
      output_dir = Path("processed/")
      
      for slide_path in slide_dir.glob("*.svs"):
          slide = Slide(slide_path, processed_path=output_dir / slide_path.stem)
          # Process each slide
          print(f"Processing: {slide.name}")
      ```
      
    • tile_extraction.md 11.8 KB
      # Tile Extraction
      
      ## Overview
      
      Tile extraction is the process of cropping smaller, manageable regions from large whole slide images. Histolab provides three main extraction strategies, each suited for different analysis needs. All tilers share common parameters and provide methods for previewing and extracting tiles.
      
      ## Common Parameters
      
      Constructor parameters shared by the tiler classes:
      
      ```python
      tile_size: tuple = (512, 512)           # Tile dimensions in pixels (width, height)
      level: int = 0                          # Pyramid level for extraction (0=highest resolution)
      check_tissue: bool = True               # Filter tiles by tissue content
      tissue_percent: float = 80.0            # Minimum tissue coverage (0-100)
      pixel_overlap: int = 0                  # Overlap between adjacent tiles (GridTiler/ScoreTiler)
      prefix: str = ""                        # Prefix for saved tile filenames
      suffix: str = ".png"                    # File extension for saved tiles
      mpp: float = None                       # Fetch by microns/pixel instead of level
      ```
      
      `extraction_mask` is **not** a constructor parameter. It is an argument of
      `extract()` and `locate_tiles()` (default `BiggestTissueBoxMask()`):
      
      ```python
      tiler.extract(slide, extraction_mask=TissueMask())
      tiler.locate_tiles(slide, extraction_mask=TissueMask())
      ```
      
      ## RandomTiler
      
      **Purpose:** Extract a fixed number of randomly positioned tiles from tissue regions.
      
      ```python
      from histolab.tiler import RandomTiler
      
      random_tiler = RandomTiler(
          tile_size=(512, 512),
          n_tiles=100,                # Number of random tiles to extract
          level=0,
          seed=42,                    # Random seed for reproducibility
          check_tissue=True,
          tissue_percent=80.0
      )
      
      # Extract tiles
      random_tiler.extract(slide, extraction_mask=TissueMask())
      ```
      
      **Key Parameters:**
      - `n_tiles`: Number of random tiles to extract
      - `seed`: Random seed for reproducible tile selection
      - `max_iter`: Maximum attempts to find valid tiles (default 1000)
      
      **Use cases:**
      - Exploratory analysis of slide content
      - Sampling diverse regions for training data
      - Quick assessment of tissue characteristics
      - Balanced dataset creation from multiple slides
      
      **Advantages:**
      - Computationally efficient
      - Good for sampling diverse tissue morphologies
      - Reproducible with seed parameter
      - Fast execution
      
      **Limitations:**
      - May miss rare tissue patterns
      - No guarantee of coverage
      - Random distribution may not capture structured features
      
      ## GridTiler
      
      **Purpose:** Extract tiles systematically across tissue regions following a grid pattern.
      
      ```python
      from histolab.tiler import GridTiler
      
      grid_tiler = GridTiler(
          tile_size=(512, 512),
          level=0,
          check_tissue=True,
          tissue_percent=80.0,
          pixel_overlap=0             # Overlap in pixels between adjacent tiles
      )
      
      # Extract tiles
      grid_tiler.extract(slide)
      ```
      
      **Key Parameters:**
      - `pixel_overlap`: Number of overlapping pixels between adjacent tiles
        - `pixel_overlap=0`: Non-overlapping tiles
        - `pixel_overlap=128`: 128-pixel overlap on each side
        - Can be used for sliding window approaches
      
      **Use cases:**
      - Comprehensive slide coverage
      - Spatial analysis requiring positional information
      - Image reconstruction from tiles
      - Semantic segmentation tasks
      - Region-based analysis
      
      **Advantages:**
      - Complete tissue coverage
      - Preserves spatial relationships
      - Predictable tile positions
      - Suitable for whole-slide analysis
      
      **Limitations:**
      - Computationally intensive for large slides
      - May generate many background-heavy tiles (mitigated by `check_tissue`)
      - Larger output datasets
      
      **Grid Pattern:**
      ```
      [Tile 1][Tile 2][Tile 3]
      [Tile 4][Tile 5][Tile 6]
      [Tile 7][Tile 8][Tile 9]
      ```
      
      With `pixel_overlap=64`:
      ```
      [Tile 1-overlap-Tile 2-overlap-Tile 3]
      [    overlap       overlap       overlap]
      [Tile 4-overlap-Tile 5-overlap-Tile 6]
      ```
      
      ## ScoreTiler
      
      **Purpose:** Extract top-ranked tiles based on custom scoring functions.
      
      ```python
      from histolab.tiler import ScoreTiler
      from histolab.scorer import NucleiScorer
      
      score_tiler = ScoreTiler(
          tile_size=(512, 512),
          n_tiles=50,                 # Number of top-scoring tiles to extract
          level=0,
          scorer=NucleiScorer(),      # Scoring function
          check_tissue=True
      )
      
      # Extract top-scoring tiles
      score_tiler.extract(slide)
      ```
      
      **Key Parameters:**
      - `n_tiles`: Number of top-scoring tiles to extract
      - `scorer`: Scoring function (e.g., `NucleiScorer`, `CellularityScorer`, custom scorer)
      
      **Use cases:**
      - Extracting most informative regions
      - Prioritizing tiles with specific features (nuclei, cells, etc.)
      - Quality-based tile selection
      - Focusing on diagnostically relevant areas
      - Training data curation
      
      **Advantages:**
      - Focuses on most informative tiles
      - Reduces dataset size while maintaining quality
      - Customizable with different scorers
      - Efficient for targeted analysis
      
      **Limitations:**
      - Slower than RandomTiler (must score all candidate tiles)
      - Requires appropriate scorer for task
      - May miss low-scoring but relevant regions
      
      ## Available Scorers
      
      ### NucleiScorer
      
      Scores tiles based on nuclei detection and density.
      
      ```python
      from histolab.scorer import NucleiScorer
      
      nuclei_scorer = NucleiScorer()
      ```
      
      **How it works:**
      1. Converts tile to grayscale
      2. Applies thresholding to detect nuclei
      3. Counts nuclei-like structures
      4. Assigns score based on nuclei density
      
      **Best for:**
      - Cell-rich tissue regions
      - Tumor detection
      - Mitosis analysis
      - Areas with high cellular content
      
      ### CellularityScorer
      
      Scores tiles based on overall cellular content.
      
      ```python
      from histolab.scorer import CellularityScorer
      
      cellularity_scorer = CellularityScorer()
      ```
      
      **Best for:**
      - Identifying cellular vs. stromal regions
      - Tumor cellularity assessment
      - Separating dense from sparse tissue areas
      
      ### Custom Scorers
      
      Create custom scoring functions for specific needs:
      
      ```python
      from histolab.scorer import Scorer
      import numpy as np
      
      class ColorVarianceScorer(Scorer):
          def __call__(self, tile):
              """Score tiles based on color variance."""
              tile_array = np.array(tile.image)
              # Calculate color variance
              variance = np.var(tile_array, axis=(0, 1)).sum()
              return variance
      
      # Use custom scorer
      variance_scorer = ColorVarianceScorer()
      score_tiler = ScoreTiler(
          tile_size=(512, 512),
          n_tiles=30,
          scorer=variance_scorer
      )
      ```
      
      ## Tile Preview with locate_tiles()
      
      Preview tile locations before extraction to validate tiler configuration:
      
      ```python
      # Preview tile locations. locate_tiles() does NOT take n_tiles — the number of
      # tiles is fixed by the tiler's constructor (n_tiles for Random/Score tilers).
      random_tiler.locate_tiles(
          slide,
          extraction_mask=TissueMask(),  # optional; defaults to BiggestTissueBoxMask
          scale_factor=32,               # optional thumbnail scaling
          outline="red",                 # optional box color
      )
      ```
      
      This displays the slide thumbnail with colored rectangles indicating tile positions.
      
      ## Extraction Workflow
      
      ### Basic Extraction
      
      ```python
      from histolab.slide import Slide
      from histolab.tiler import RandomTiler
      
      # Load slide
      slide = Slide("slide.svs", processed_path="output/tiles/")
      
      # Configure tiler
      tiler = RandomTiler(
          tile_size=(512, 512),
          n_tiles=100,
          level=0,
          seed=42
      )
      
      # Extract tiles (saved to processed_path)
      tiler.extract(slide)
      ```
      
      ### Extraction with Logging
      
      ```python
      import logging
      
      # Enable logging
      logging.basicConfig(level=logging.INFO)
      
      # Extract tiles with progress information
      tiler.extract(slide)
      # Output: INFO: Tile 1/100 saved...
      # Output: INFO: Tile 2/100 saved...
      ```
      
      ### Extraction with Report
      
      ```python
      # Generate CSV report with tile information
      score_tiler = ScoreTiler(
          tile_size=(512, 512),
          n_tiles=50,
          scorer=NucleiScorer()
      )
      
      # Extract and save report
      score_tiler.extract(slide, report_path="tiles_report.csv")
      
      # Report contains the filename, raw score, and the 0-1 scaled score per tile.
      ```
      
      Report format (histolab 0.7.0 — columns are exactly these three):
      ```csv
      filename,score,scaled_score
      tile_0_level0_10240-5120-10752-5632.png,0.89,1.0
      tile_1_level0_15360-7680-15872-8192.png,0.85,0.93
      ...
      ```
      
      The tile filename encodes the level and the level-0 bounding box coordinates
      (`{prefix}tile_{i}_level{level}_{x_ul}-{y_ul}-{x_br}-{y_br}{suffix}`), so
      positions are recoverable from the filename rather than separate CSV columns.
      
      ## Advanced Extraction Patterns
      
      ### Multi-Level Extraction
      
      Extract tiles at different magnification levels:
      
      ```python
      # High resolution tiles (level 0)
      high_res_tiler = RandomTiler(tile_size=(512, 512), n_tiles=50, level=0)
      high_res_tiler.extract(slide)
      
      # Medium resolution tiles (level 1)
      med_res_tiler = RandomTiler(tile_size=(512, 512), n_tiles=50, level=1)
      med_res_tiler.extract(slide)
      
      # Low resolution tiles (level 2)
      low_res_tiler = RandomTiler(tile_size=(512, 512), n_tiles=50, level=2)
      low_res_tiler.extract(slide)
      ```
      
      ### Hierarchical Extraction
      
      Extract at multiple scales from same locations:
      
      ```python
      # Extract random locations at level 0
      random_tiler_l0 = RandomTiler(
          tile_size=(512, 512),
          n_tiles=30,
          level=0,
          seed=42,
          prefix="level0_"
      )
      random_tiler_l0.extract(slide)
      
      # Extract same locations at level 1 (use same seed)
      random_tiler_l1 = RandomTiler(
          tile_size=(512, 512),
          n_tiles=30,
          level=1,
          seed=42,
          prefix="level1_"
      )
      random_tiler_l1.extract(slide)
      ```
      
      ### Custom Tile Filtering
      
      Apply additional filtering after extraction:
      
      ```python
      import cv2
      from PIL import Image
      import numpy as np
      from pathlib import Path
      
      def filter_blurry_tiles(tile_dir, threshold=100):
          """Remove blurry tiles using Laplacian variance."""
          for tile_path in Path(tile_dir).glob("*.png"):
              img = Image.open(tile_path)
              gray = np.array(img.convert('L'))
              laplacian_var = cv2.Laplacian(gray, cv2.CV_64F).var()
      
              if laplacian_var < threshold:
                  tile_path.unlink()  # Remove blurry tile
                  print(f"Removed blurry tile: {tile_path.name}")
      
      # Use after extraction
      tiler.extract(slide)
      filter_blurry_tiles("output/tiles/")
      ```
      
      ## Best Practices
      
      1. **Preview before extraction**: Always use `locate_tiles()` to verify tile placement
      2. **Use appropriate level**: Match extraction level to analysis resolution requirements
      3. **Set tissue_percent threshold**: Adjust based on staining and tissue type (70-90% typical)
      4. **Choose right tiler**:
         - RandomTiler for sampling and exploration
         - GridTiler for comprehensive coverage
         - ScoreTiler for targeted, quality-driven extraction
      5. **Enable logging**: Monitor extraction progress for large datasets
      6. **Use seeds for reproducibility**: Set random seeds in RandomTiler
      7. **Consider storage**: GridTiler can generate thousands of tiles per slide
      8. **Validate tile quality**: Check extracted tiles for artifacts, blur, or focus issues
      
      ## Performance Optimization
      
      1. **Extract at appropriate level**: Lower levels (1, 2) extract faster
      2. **Adjust tissue_percent**: Higher thresholds reduce invalid tile attempts
      3. **Use BiggestTissueBoxMask**: Faster than TissueMask for single tissue sections
      4. **Limit n_tiles**: For RandomTiler and ScoreTiler
      5. **Use pixel_overlap=0**: For non-overlapping GridTiler extraction
      
      ## Troubleshooting
      
      ### Issue: No tiles extracted
      **Solutions:**
      - Lower `tissue_percent` threshold
      - Verify slide contains tissue (check thumbnail)
      - Ensure extraction_mask captures tissue regions
      - Check that tile_size is appropriate for slide resolution
      
      ### Issue: Many background tiles extracted
      **Solutions:**
      - Enable `check_tissue=True`
      - Increase `tissue_percent` threshold
      - Use appropriate mask (TissueMask vs. BiggestTissueBoxMask)
      
      ### Issue: Extraction is very slow
      **Solutions:**
      - Extract at lower pyramid level (level=1 or 2)
      - Reduce `n_tiles` for RandomTiler/ScoreTiler
      - Use RandomTiler instead of GridTiler for sampling
      - Use BiggestTissueBoxMask instead of TissueMask
      
      ### Issue: Tiles have too much overlap (GridTiler)
      **Solutions:**
      - Set `pixel_overlap=0` for non-overlapping tiles
      - Reduce `pixel_overlap` value
      
    • tissue_masks.md 7.3 KB
      # Tissue Masks
      
      ## Overview
      
      Tissue masks are binary representations that identify tissue regions within whole slide images. They are essential for filtering out background, artifacts, and non-tissue areas during tile extraction. Histolab provides several mask classes to accommodate different tissue segmentation needs.
      
      ## Mask Classes
      
      ### BinaryMask
      
      **Purpose:** Generic base class for creating custom binary masks.
      
      ```python
      from histolab.masks import BinaryMask
      
      class CustomMask(BinaryMask):
          def _mask(self, obj):
              # Implement custom masking logic
              # Return binary numpy array
              pass
      ```
      
      **Use cases:**
      - Custom tissue segmentation algorithms
      - Region-specific analysis (e.g., excluding annotations)
      - Integration with external segmentation models
      
      ### TissueMask
      
      **Purpose:** Segments all tissue regions in the slide using automated filters.
      
      ```python
      from histolab.masks import TissueMask
      
      # Create tissue mask
      tissue_mask = TissueMask()
      
      # Apply to slide
      mask_array = tissue_mask(slide)
      ```
      
      **How it works:**
      1. Converts image to grayscale
      2. Applies Otsu thresholding to separate tissue from background
      3. Performs binary dilation to connect nearby tissue regions
      4. Removes small holes within tissue regions
      5. Filters out small objects (artifacts)
      
      **Returns:** Binary NumPy array where:
      - `True` (or 1): Tissue pixels
      - `False` (or 0): Background pixels
      
      **Best for:**
      - Slides with multiple separate tissue sections
      - Comprehensive tissue analysis
      - When all tissue regions are important
      
      ### BiggestTissueBoxMask (Default)
      
      **Purpose:** Identifies and returns the bounding box of the largest connected tissue region.
      
      ```python
      from histolab.masks import BiggestTissueBoxMask
      
      # Create mask for largest tissue region
      biggest_mask = BiggestTissueBoxMask()
      
      # Apply to slide
      mask_array = biggest_mask(slide)
      ```
      
      **How it works:**
      1. Applies same filtering pipeline as TissueMask
      2. Identifies all connected tissue components
      3. Selects the largest connected component
      4. Returns bounding box encompassing that region
      
      **Best for:**
      - Slides with a single primary tissue section
      - Excluding small artifacts or tissue fragments
      - Focusing on main tissue area (default for most tilers)
      
      ## Customizing Masks with Filters
      
      Masks accept custom filters for specialized tissue detection. Pass the filters
      as **positional varargs** (`TissueMask(*filters)`) — not as a `filters=` keyword
      and not as a single `Compose` object. They replace the default filter chain.
      
      ```python
      from histolab.masks import TissueMask
      from histolab.filters.image_filters import RgbToGrayscale, OtsuThreshold
      from histolab.filters.morphological_filters import BinaryDilation, RemoveSmallHoles
      
      custom_mask = TissueMask(
          RgbToGrayscale(),
          OtsuThreshold(),
          BinaryDilation(disk_size=5),
          RemoveSmallHoles(area_threshold=500),
      )
      ```
      
      ## Visualizing Masks
      
      ### Using locate_mask()
      
      ```python
      from histolab.slide import Slide
      from histolab.masks import TissueMask
      
      slide = Slide("slide.svs", processed_path="output/")
      mask = TissueMask()
      
      # Visualize mask boundaries on thumbnail
      slide.locate_mask(mask)
      ```
      
      This displays the slide thumbnail with mask boundaries overlaid in a contrasting color.
      
      ### Manual Visualization
      
      ```python
      import matplotlib.pyplot as plt
      from histolab.masks import TissueMask
      
      slide = Slide("slide.svs", processed_path="output/")
      tissue_mask = TissueMask()
      
      # Generate mask
      mask_array = tissue_mask(slide)
      
      # Plot side by side
      fig, axes = plt.subplots(1, 2, figsize=(15, 7))
      
      axes[0].imshow(slide.thumbnail)
      axes[0].set_title("Original Slide")
      axes[0].axis('off')
      
      axes[1].imshow(mask_array, cmap='gray')
      axes[1].set_title("Tissue Mask")
      axes[1].axis('off')
      
      plt.show()
      ```
      
      ## Creating Custom Rectangular Masks
      
      Define specific regions of interest:
      
      ```python
      from histolab.masks import BinaryMask
      import numpy as np
      
      class RectangularMask(BinaryMask):
          def __init__(self, x_start, y_start, width, height):
              self.x_start = x_start
              self.y_start = y_start
              self.width = width
              self.height = height
      
          def _mask(self, obj):
              # Create mask with specified rectangular region
              thumb = obj.thumbnail
              mask = np.zeros(thumb.shape[:2], dtype=bool)
              mask[self.y_start:self.y_start+self.height,
                   self.x_start:self.x_start+self.width] = True
              return mask
      
      # Use custom mask
      roi_mask = RectangularMask(x_start=1000, y_start=500, width=2000, height=1500)
      ```
      
      ## Excluding Annotations
      
      Pathology slides often contain pen markings or digital annotations. Exclude them using custom masks:
      
      ```python
      import cv2
      import numpy as np
      from histolab.masks import TissueMask, BinaryMask
      
      class AnnotationExclusionMask(BinaryMask):
          def _mask(self, obj):
              # Run the default tissue detection first; its output sets the target shape.
              tissue_mask = TissueMask()(obj)            # bool ndarray (H, W)
              h, w = tissue_mask.shape[:2]
      
              # Build a pen-mark mask at the SAME resolution as tissue_mask so the
              # arrays line up for the boolean combine below.
              thumb = np.array(obj.thumbnail.convert("RGB"))
              thumb = cv2.resize(thumb, (w, h), interpolation=cv2.INTER_NEAREST)
              hsv = cv2.cvtColor(thumb, cv2.COLOR_RGB2HSV)
      
              # HSV range for blue/green pen marks (OpenCV hue is 0-179)
              lower_blue = np.array([100, 50, 50])
              upper_blue = np.array([130, 255, 255])
              pen_mask = cv2.inRange(hsv, lower_blue, upper_blue).astype(bool)
      
              # Keep tissue, drop pen marks
              return tissue_mask & ~pen_mask
      ```
      
      ## Integration with Tile Extraction
      
      Masks integrate with tilers through the `extraction_mask` parameter of
      `extract()` / `locate_tiles()` — **not** the tiler constructor:
      
      ```python
      from histolab.tiler import RandomTiler
      from histolab.masks import TissueMask, BiggestTissueBoxMask
      
      random_tiler = RandomTiler(tile_size=(512, 512), n_tiles=100, level=0, seed=42)
      
      # Extract from ALL tissue sections
      random_tiler.extract(slide, extraction_mask=TissueMask())
      
      # Default behavior (largest tissue bounding box) — extraction_mask defaults to
      # BiggestTissueBoxMask(), so this is equivalent to omitting it
      random_tiler.extract(slide, extraction_mask=BiggestTissueBoxMask())
      ```
      
      ## Best Practices
      
      1. **Preview masks before extraction**: Use `locate_mask()` or manual visualization to verify mask quality
      2. **Choose appropriate mask type**: Use `TissueMask` for multiple tissue sections, `BiggestTissueBoxMask` for single main sections
      3. **Customize for specific stains**: Different stains (H&E, IHC) may require adjusted threshold parameters
      4. **Handle artifacts**: Use custom filters or masks to exclude pen marks, bubbles, or folds
      5. **Test on diverse slides**: Validate mask performance across slides with varying quality and artifacts
      6. **Consider computational cost**: `TissueMask` is more comprehensive but computationally intensive than `BiggestTissueBoxMask`
      
      ## Common Issues and Solutions
      
      ### Issue: Mask includes too much background
      **Solution:** Adjust Otsu threshold or increase small object removal threshold
      
      ### Issue: Mask excludes valid tissue
      **Solution:** Reduce small object removal threshold or modify dilation parameters
      
      ### Issue: Multiple tissue sections, but only largest is captured
      **Solution:** Switch from `BiggestTissueBoxMask` to `TissueMask`
      
      ### Issue: Pen annotations included in mask
      **Solution:** Implement custom annotation exclusion mask (see example above)
      
    • visualization.md 13.8 KB
      # Visualization
      
      ## Overview
      
      Histolab provides several built-in visualization methods to help inspect slides, preview tile locations, visualize masks, and assess extraction quality. Proper visualization is essential for validating preprocessing pipelines, debugging extraction issues, and presenting results.
      
      ## Slide Visualization
      
      ### Thumbnail Display
      
      ```python
      from histolab.slide import Slide
      import matplotlib.pyplot as plt
      
      slide = Slide("slide.svs", processed_path="output/")
      
      # Display thumbnail
      plt.figure(figsize=(10, 10))
      plt.imshow(slide.thumbnail)
      plt.title(f"Slide: {slide.name}")
      plt.axis('off')
      plt.show()
      ```
      
      ### Save Thumbnail to Disk
      
      ```python
      # `thumbnail` is a PIL.Image — save it directly (there is no save_thumbnail()).
      slide.thumbnail.save("output/thumbnail.png")
      ```
      
      ### Scaled Images
      
      ```python
      # Get scaled version of slide at specific downsample factor
      scaled_img = slide.scaled_image(scale_factor=32)
      
      plt.imshow(scaled_img)
      plt.title(f"Slide at 32x downsample")
      plt.show()
      ```
      
      ## Mask Visualization
      
      ### Using locate_mask()
      
      ```python
      from histolab.masks import TissueMask, BiggestTissueBoxMask
      
      # Visualize TissueMask
      tissue_mask = TissueMask()
      slide.locate_mask(tissue_mask)
      
      # Visualize BiggestTissueBoxMask
      biggest_mask = BiggestTissueBoxMask()
      slide.locate_mask(biggest_mask)
      ```
      
      This displays the slide thumbnail with mask boundaries overlaid in red.
      
      ### Manual Mask Visualization
      
      ```python
      import matplotlib.pyplot as plt
      from histolab.masks import TissueMask
      
      slide = Slide("slide.svs", processed_path="output/")
      mask = TissueMask()
      
      # Generate mask
      mask_array = mask(slide)
      
      # Create side-by-side comparison
      fig, axes = plt.subplots(1, 3, figsize=(20, 7))
      
      # Original thumbnail
      axes[0].imshow(slide.thumbnail)
      axes[0].set_title("Original Slide")
      axes[0].axis('off')
      
      # Binary mask
      axes[1].imshow(mask_array, cmap='gray')
      axes[1].set_title("Tissue Mask")
      axes[1].axis('off')
      
      # Overlay mask on thumbnail
      from matplotlib.colors import ListedColormap
      overlay = slide.thumbnail.copy()
      axes[2].imshow(overlay)
      axes[2].imshow(mask_array, cmap=ListedColormap(['none', 'red']), alpha=0.3)
      axes[2].set_title("Mask Overlay")
      axes[2].axis('off')
      
      plt.tight_layout()
      plt.show()
      ```
      
      ### Comparing Multiple Masks
      
      ```python
      from histolab.masks import TissueMask, BiggestTissueBoxMask
      
      masks = {
          'TissueMask': TissueMask(),
          'BiggestTissueBoxMask': BiggestTissueBoxMask()
      }
      
      fig, axes = plt.subplots(1, len(masks) + 1, figsize=(20, 6))
      
      # Original
      axes[0].imshow(slide.thumbnail)
      axes[0].set_title("Original")
      axes[0].axis('off')
      
      # Each mask
      for idx, (name, mask) in enumerate(masks.items(), 1):
          mask_array = mask(slide)
          axes[idx].imshow(mask_array, cmap='gray')
          axes[idx].set_title(name)
          axes[idx].axis('off')
      
      plt.tight_layout()
      plt.show()
      ```
      
      ## Tile Location Preview
      
      ### Using locate_tiles()
      
      Preview tile locations before extraction:
      
      ```python
      from histolab.tiler import RandomTiler, GridTiler, ScoreTiler
      from histolab.scorer import NucleiScorer
      
      # RandomTiler preview (n_tiles is on the constructor; locate_tiles takes no n_tiles)
      random_tiler = RandomTiler(
          tile_size=(512, 512),
          n_tiles=50,
          level=0,
          seed=42
      )
      random_tiler.locate_tiles(slide)
      
      # GridTiler preview
      grid_tiler = GridTiler(
          tile_size=(512, 512),
          level=0
      )
      grid_tiler.locate_tiles(slide)
      
      # ScoreTiler preview
      score_tiler = ScoreTiler(
          tile_size=(512, 512),
          n_tiles=30,
          scorer=NucleiScorer()
      )
      score_tiler.locate_tiles(slide)
      ```
      
      This displays colored rectangles on the slide thumbnail indicating where tiles will be extracted.
      
      ### Custom Tile Location Visualization
      
      ```python
      import matplotlib.pyplot as plt
      import matplotlib.patches as patches
      from histolab.tiler import RandomTiler
      
      slide = Slide("slide.svs", processed_path="output/")
      tiler = RandomTiler(tile_size=(512, 512), n_tiles=30, seed=42)
      
      # Get thumbnail and scale factor
      thumbnail = slide.thumbnail
      scale_factor = slide.dimensions[0] / thumbnail.size[0]
      
      # Generate tile coordinates (without extracting)
      fig, ax = plt.subplots(figsize=(12, 12))
      ax.imshow(thumbnail)
      ax.set_title("Tile Locations Preview")
      ax.axis('off')
      
      # Manually add rectangles for each tile location
      # Note: This is conceptual - actual implementation would retrieve coordinates from tiler
      tile_coords = []  # Would be populated by tiler logic
      for coord in tile_coords:
          x, y = coord[0] / scale_factor, coord[1] / scale_factor
          w, h = 512 / scale_factor, 512 / scale_factor
          rect = patches.Rectangle((x, y), w, h,
                                   linewidth=2, edgecolor='red',
                                   facecolor='none')
          ax.add_patch(rect)
      
      plt.show()
      ```
      
      ## Tile Visualization
      
      ### Display Extracted Tiles
      
      ```python
      from pathlib import Path
      from PIL import Image
      import matplotlib.pyplot as plt
      
      tile_dir = Path("output/tiles/")
      tile_paths = list(tile_dir.glob("*.png"))[:16]  # First 16 tiles
      
      fig, axes = plt.subplots(4, 4, figsize=(12, 12))
      axes = axes.ravel()
      
      for idx, tile_path in enumerate(tile_paths):
          tile_img = Image.open(tile_path)
          axes[idx].imshow(tile_img)
          axes[idx].set_title(tile_path.stem, fontsize=8)
          axes[idx].axis('off')
      
      plt.tight_layout()
      plt.show()
      ```
      
      ### Tile Grid Mosaic
      
      ```python
      def create_tile_mosaic(tile_dir, grid_size=(4, 4)):
          """Create mosaic of tiles."""
          tile_paths = list(Path(tile_dir).glob("*.png"))[:grid_size[0] * grid_size[1]]
      
          fig, axes = plt.subplots(grid_size[0], grid_size[1], figsize=(16, 16))
      
          for idx, tile_path in enumerate(tile_paths):
              row = idx // grid_size[1]
              col = idx % grid_size[1]
              tile_img = Image.open(tile_path)
              axes[row, col].imshow(tile_img)
              axes[row, col].axis('off')
      
          plt.tight_layout()
          plt.savefig("tile_mosaic.png", dpi=150, bbox_inches='tight')
          plt.show()
      
      create_tile_mosaic("output/tiles/", grid_size=(5, 5))
      ```
      
      ### Tile with Tissue Mask Overlay
      
      ```python
      from histolab.tile import Tile
      import matplotlib.pyplot as plt
      
      # Assume we have a tile object
      tile = Tile(image=pil_image, coords=(x, y))
      
      # Calculate tissue mask
      tile.calculate_tissue_mask()
      
      fig, axes = plt.subplots(1, 3, figsize=(15, 5))
      
      # Original tile
      axes[0].imshow(tile.image)
      axes[0].set_title("Original Tile")
      axes[0].axis('off')
      
      # Tissue mask
      axes[1].imshow(tile.tissue_mask, cmap='gray')
      axes[1].set_title(f"Tissue Mask ({tile.tissue_ratio:.1%} tissue)")
      axes[1].axis('off')
      
      # Overlay
      axes[2].imshow(tile.image)
      axes[2].imshow(tile.tissue_mask, cmap='Reds', alpha=0.3)
      axes[2].set_title("Overlay")
      axes[2].axis('off')
      
      plt.tight_layout()
      plt.show()
      ```
      
      ## Quality Assessment Visualization
      
      ### Tile Score Distribution
      
      ```python
      import pandas as pd
      import matplotlib.pyplot as plt
      import seaborn as sns
      
      # Load tile report from ScoreTiler
      report_df = pd.read_csv("tiles_report.csv")
      
      # Score distribution histogram
      plt.figure(figsize=(10, 6))
      plt.hist(report_df['score'], bins=30, edgecolor='black', alpha=0.7)
      plt.xlabel('Tile Score')
      plt.ylabel('Frequency')
      plt.title('Distribution of Tile Scores')
      plt.grid(axis='y', alpha=0.3)
      plt.show()
      
      # Raw score vs scaled (0-1) score — the two numeric columns the report provides
      plt.figure(figsize=(10, 6))
      plt.scatter(report_df['score'], report_df['scaled_score'], alpha=0.5)
      plt.xlabel('Raw Score')
      plt.ylabel('Scaled Score (0-1)')
      plt.title('Raw vs Scaled Tile Score')
      plt.grid(alpha=0.3)
      plt.show()
      ```
      
      ### Top vs Bottom Scoring Tiles
      
      ```python
      import pandas as pd
      from PIL import Image
      import matplotlib.pyplot as plt
      
      # Load tile report
      report_df = pd.read_csv("tiles_report.csv")
      report_df = report_df.sort_values('score', ascending=False)
      
      # Top 8 tiles
      top_tiles = report_df.head(8)
      # Bottom 8 tiles
      bottom_tiles = report_df.tail(8)
      
      fig, axes = plt.subplots(2, 8, figsize=(20, 6))
      
      # Display top tiles (the report column is 'filename', not 'tile_name')
      for idx, (_, row) in enumerate(top_tiles.iterrows()):
          tile_img = Image.open(f"output/tiles/{row['filename']}")
          axes[0, idx].imshow(tile_img)
          axes[0, idx].set_title(f"Score: {row['score']:.3f}", fontsize=8)
          axes[0, idx].axis('off')
      
      # Display bottom tiles
      for idx, (_, row) in enumerate(bottom_tiles.iterrows()):
          tile_img = Image.open(f"output/tiles/{row['filename']}")
          axes[1, idx].imshow(tile_img)
          axes[1, idx].set_title(f"Score: {row['score']:.3f}", fontsize=8)
          axes[1, idx].axis('off')
      
      axes[0, 0].set_ylabel('Top Scoring', fontsize=12)
      axes[1, 0].set_ylabel('Bottom Scoring', fontsize=12)
      
      plt.tight_layout()
      plt.savefig("score_comparison.png", dpi=150, bbox_inches='tight')
      plt.show()
      ```
      
      ## Multi-Slide Visualization
      
      ### Slide Collection Thumbnails
      
      ```python
      from pathlib import Path
      from histolab.slide import Slide
      import matplotlib.pyplot as plt
      
      slide_dir = Path("slides/")
      slide_paths = list(slide_dir.glob("*.svs"))[:9]
      
      fig, axes = plt.subplots(3, 3, figsize=(15, 15))
      axes = axes.ravel()
      
      for idx, slide_path in enumerate(slide_paths):
          slide = Slide(slide_path, processed_path="output/")
          axes[idx].imshow(slide.thumbnail)
          axes[idx].set_title(slide.name, fontsize=10)
          axes[idx].axis('off')
      
      plt.tight_layout()
      plt.savefig("slide_collection.png", dpi=150, bbox_inches='tight')
      plt.show()
      ```
      
      ### Tissue Coverage Comparison
      
      ```python
      from pathlib import Path
      from histolab.slide import Slide
      from histolab.masks import TissueMask
      import matplotlib.pyplot as plt
      import numpy as np
      
      slide_paths = list(Path("slides/").glob("*.svs"))
      tissue_percentages = []
      slide_names = []
      
      for slide_path in slide_paths:
          slide = Slide(slide_path, processed_path="output/")
          mask = TissueMask()(slide)
          tissue_pct = mask.sum() / mask.size * 100
          tissue_percentages.append(tissue_pct)
          slide_names.append(slide.name)
      
      # Bar plot
      plt.figure(figsize=(12, 6))
      plt.bar(range(len(slide_names)), tissue_percentages)
      plt.xticks(range(len(slide_names)), slide_names, rotation=45, ha='right')
      plt.ylabel('Tissue Coverage (%)')
      plt.title('Tissue Coverage Across Slides')
      plt.grid(axis='y', alpha=0.3)
      plt.tight_layout()
      plt.show()
      ```
      
      ## Filter Effect Visualization
      
      ### Before and After Filtering
      
      ```python
      from histolab.filters.image_filters import (
          RgbToGrayscale, HistogramEqualization, Compose
      )
      
      # Define filter pipeline
      filter_pipeline = Compose([
          RgbToGrayscale(),
          HistogramEqualization()
      ])
      
      # Original vs filtered
      fig, axes = plt.subplots(1, 2, figsize=(12, 6))
      
      axes[0].imshow(slide.thumbnail)
      axes[0].set_title("Original")
      axes[0].axis('off')
      
      filtered = filter_pipeline(slide.thumbnail)
      axes[1].imshow(filtered, cmap='gray')
      axes[1].set_title("After Filtering")
      axes[1].axis('off')
      
      plt.tight_layout()
      plt.show()
      ```
      
      ### Multi-Step Filter Visualization
      
      ```python
      from histolab.filters.image_filters import RgbToGrayscale, OtsuThreshold, Compose
      from histolab.filters.morphological_filters import BinaryDilation, RemoveSmallObjects
      
      # Individual filter steps
      steps = [
          ("Original", None),
          ("Grayscale", RgbToGrayscale()),
          ("Otsu Threshold", Compose([RgbToGrayscale(), OtsuThreshold()])),
          ("After Dilation", Compose([RgbToGrayscale(), OtsuThreshold(), BinaryDilation(disk_size=5)])),
          ("Remove Small Objects", Compose([RgbToGrayscale(), OtsuThreshold(), BinaryDilation(disk_size=5), RemoveSmallObjects(min_size=500)]))
      ]
      
      fig, axes = plt.subplots(1, len(steps), figsize=(20, 4))
      
      for idx, (title, filter_fn) in enumerate(steps):
          if filter_fn is None:
              axes[idx].imshow(slide.thumbnail)
          else:
              result = filter_fn(slide.thumbnail)
              axes[idx].imshow(result, cmap='gray')
          axes[idx].set_title(title, fontsize=10)
          axes[idx].axis('off')
      
      plt.tight_layout()
      plt.show()
      ```
      
      ## Exporting Visualizations
      
      ### High-Resolution Exports
      
      ```python
      # Export high-resolution figure
      fig, ax = plt.subplots(figsize=(20, 20))
      ax.imshow(slide.thumbnail)
      ax.axis('off')
      plt.savefig("slide_high_res.png", dpi=300, bbox_inches='tight', pad_inches=0)
      plt.close()
      ```
      
      ### PDF Reports
      
      ```python
      from matplotlib.backends.backend_pdf import PdfPages
      
      # Create multi-page PDF report
      with PdfPages('slide_report.pdf') as pdf:
          # Page 1: Slide thumbnail
          fig1, ax1 = plt.subplots(figsize=(10, 10))
          ax1.imshow(slide.thumbnail)
          ax1.set_title(f"Slide: {slide.name}")
          ax1.axis('off')
          pdf.savefig(fig1, bbox_inches='tight')
          plt.close()
      
          # Page 2: Tissue mask
          fig2, ax2 = plt.subplots(figsize=(10, 10))
          mask = TissueMask()(slide)
          ax2.imshow(mask, cmap='gray')
          ax2.set_title("Tissue Mask")
          ax2.axis('off')
          pdf.savefig(fig2, bbox_inches='tight')
          plt.close()
      
          # Page 3: Tile locations
          fig3, ax3 = plt.subplots(figsize=(10, 10))
          tiler = RandomTiler(tile_size=(512, 512), n_tiles=30)
          tiler.locate_tiles(slide)
          pdf.savefig(fig3, bbox_inches='tight')
          plt.close()
      ```
      
      ## Interactive Visualization (Jupyter)
      
      ### IPython Widgets for Exploration
      
      ```python
      from ipywidgets import interact, IntSlider
      import matplotlib.pyplot as plt
      from histolab.filters.image_filters import RgbToGrayscale, OtsuThreshold, Compose
      from histolab.filters.morphological_filters import BinaryDilation
      
      @interact(disk_size=IntSlider(min=1, max=20, value=5))
      def explore_dilation(disk_size):
          """Interactive dilation exploration."""
          filter_pipeline = Compose([
              RgbToGrayscale(),
              OtsuThreshold(),
              BinaryDilation(disk_size=disk_size)
          ])
          result = filter_pipeline(slide.thumbnail)
      
          plt.figure(figsize=(10, 10))
          plt.imshow(result, cmap='gray')
          plt.title(f"Binary Dilation (disk_size={disk_size})")
          plt.axis('off')
          plt.show()
      ```
      
      ## Best Practices
      
      1. **Always preview before processing**: Use thumbnails and `locate_tiles()` to validate settings
      2. **Use side-by-side comparisons**: Show before/after for filter effects
      3. **Label clearly**: Include titles, axes labels, and legends
      4. **Export high-resolution**: Use 300 DPI for publication-quality figures
      5. **Save intermediate visualizations**: Document processing steps
      6. **Use colormaps appropriately**: 'gray' for binary masks, 'viridis' for heatmaps
      7. **Create reusable visualization functions**: Standardize reporting across projects
      
    • workflows.md 9.7 KB
      # Histolab Worked Workflows
      
      End-to-end, copy-pasteable pipelines for histolab 0.7.0. Each is self-contained.
      
      API reminders used throughout (verified against histolab 0.7.0):
      - `n_tiles`, `seed`, `level`, `tile_size`, `check_tissue`, `tissue_percent` are
        **constructor** args. `locate_tiles()`/`extract()` take only `slide`, an
        optional `extraction_mask`, and logging/styling kwargs — no `n_tiles`.
      - `Compose` lives in `histolab.filters.image_filters`, not `.compositions`.
      - `TissueMask`/`BiggestTissueBoxMask` take custom filters as positional varargs,
        e.g. `TissueMask(RgbToGrayscale(), OtsuThreshold(), ...)` — not `filters=`.
      - There is no `slide.save_thumbnail()`; use `slide.thumbnail.save(path)`.
      - The `ScoreTiler` report CSV has columns `filename, score, scaled_score`.
      
      ## Quick Start
      
      Basic workflow for extracting tiles from a whole slide image:
      
      ```python
      from histolab.slide import Slide
      from histolab.tiler import RandomTiler
      
      # Load slide
      slide = Slide("slide.svs", processed_path="output/")
      
      # Configure tiler
      tiler = RandomTiler(
          tile_size=(512, 512),
          n_tiles=100,
          level=0,
          seed=42
      )
      
      # Preview tile locations
      tiler.locate_tiles(slide)
      
      # Extract tiles
      tiler.extract(slide)
      ```
      
      ## Slide management example
      
      ```python
      from histolab.slide import Slide
      from histolab.data import prostate_tissue
      
      # Load sample data (returns an OpenSlide object and a path)
      prostate_svs, prostate_path = prostate_tissue()
      
      # Initialize slide
      slide = Slide(prostate_path, processed_path="output/")
      
      # Inspect properties
      print(f"Dimensions: {slide.dimensions}")          # (width, height) at level 0
      print(f"Levels: {slide.levels}")                  # list of available levels
      print(f"Microns/pixel: {slide.base_mpp}")
      print(f"Magnification: {slide.properties.get('openslide.objective-power')}")
      
      # Save thumbnail (thumbnail is a PIL image; there is no save_thumbnail method)
      slide.thumbnail.save("output/thumbnail.png")
      ```
      
      ## Tissue mask example
      
      ```python
      from histolab.masks import TissueMask, BiggestTissueBoxMask
      
      # Create tissue mask for all tissue regions
      tissue_mask = TissueMask()
      
      # Visualize mask on slide
      slide.locate_mask(tissue_mask)
      
      # Get mask array
      mask_array = tissue_mask(slide)
      
      # Use largest tissue region (default for most extractors)
      biggest_mask = BiggestTissueBoxMask()
      ```
      
      When to use each mask:
      - `TissueMask`: Multiple tissue sections, comprehensive analysis
      - `BiggestTissueBoxMask`: Single main tissue section, exclude artifacts (default)
      - Custom `BinaryMask`: Specific ROI, exclude annotations, custom segmentation
      
      ## Tile extraction — three strategies
      
      ```python
      from histolab.tiler import RandomTiler, GridTiler, ScoreTiler
      from histolab.scorer import NucleiScorer
      
      # Random sampling (fast, diverse)
      random_tiler = RandomTiler(
          tile_size=(512, 512),
          n_tiles=100,
          level=0,
          seed=42,
          check_tissue=True,
          tissue_percent=80.0
      )
      random_tiler.extract(slide)
      
      # Grid coverage (comprehensive)
      grid_tiler = GridTiler(
          tile_size=(512, 512),
          level=0,
          pixel_overlap=0,
          check_tissue=True
      )
      grid_tiler.extract(slide)
      
      # Score-based selection (most informative)
      score_tiler = ScoreTiler(
          tile_size=(512, 512),
          n_tiles=50,
          scorer=NucleiScorer(),
          level=0
      )
      score_tiler.extract(slide, report_path="tiles_report.csv")
      ```
      
      Always preview before extracting:
      
      ```python
      # Preview tile locations on thumbnail (no n_tiles arg — set it on the tiler)
      tiler.locate_tiles(slide)
      ```
      
      ## Filters / preprocessing example
      
      ```python
      from histolab.filters.image_filters import RgbToGrayscale, OtsuThreshold, Compose
      from histolab.filters.morphological_filters import (
          BinaryDilation, RemoveSmallHoles, RemoveSmallObjects
      )
      
      # Standard tissue detection pipeline (Compose takes a list of filters)
      tissue_detection = Compose([
          RgbToGrayscale(),
          OtsuThreshold(),
          BinaryDilation(disk_size=5),
          RemoveSmallHoles(area_threshold=1000),
          RemoveSmallObjects(min_size=500)
      ])
      
      # Use as a custom mask: TissueMask takes individual filters as *args, not a
      # Compose object and not a filters= kwarg.
      from histolab.masks import TissueMask
      custom_mask = TissueMask(
          RgbToGrayscale(),
          OtsuThreshold(),
          BinaryDilation(disk_size=5),
          RemoveSmallHoles(area_threshold=1000),
          RemoveSmallObjects(min_size=500),
      )
      
      # Apply a composed filter to a tile
      from histolab.tile import Tile
      filtered_tile = tile.apply_filters(tissue_detection)
      ```
      
      ## Visualization example
      
      ```python
      import matplotlib.pyplot as plt
      from histolab.masks import TissueMask
      
      # Display slide thumbnail
      plt.figure(figsize=(10, 10))
      plt.imshow(slide.thumbnail)
      plt.title(f"Slide: {slide.name}")
      plt.axis('off')
      plt.show()
      
      # Visualize tissue mask
      tissue_mask = TissueMask()
      slide.locate_mask(tissue_mask)
      
      # Preview tile locations (n_tiles is set on the tiler, not on locate_tiles)
      tiler = RandomTiler(tile_size=(512, 512), n_tiles=50)
      tiler.locate_tiles(slide)
      
      # Display extracted tiles in grid
      from pathlib import Path
      from PIL import Image
      
      tile_paths = list(Path("output/tiles/").glob("*.png"))[:16]
      fig, axes = plt.subplots(4, 4, figsize=(12, 12))
      axes = axes.ravel()
      
      for idx, tile_path in enumerate(tile_paths):
          tile_img = Image.open(tile_path)
          axes[idx].imshow(tile_img)
          axes[idx].set_title(tile_path.stem, fontsize=8)
          axes[idx].axis('off')
      
      plt.tight_layout()
      plt.show()
      ```
      
      ## Workflow 1: Exploratory Tile Extraction
      
      Quick sampling of diverse tissue regions for initial analysis.
      
      ```python
      from histolab.slide import Slide
      from histolab.tiler import RandomTiler
      import logging
      
      # Enable logging for progress tracking
      logging.basicConfig(level=logging.INFO)
      
      # Load slide
      slide = Slide("slide.svs", processed_path="output/random_tiles/")
      
      # Inspect slide
      print(f"Dimensions: {slide.dimensions}")
      print(f"Levels: {slide.levels}")
      slide.thumbnail.save("output/random_tiles/thumbnail.png")
      
      # Configure random tiler
      random_tiler = RandomTiler(
          tile_size=(512, 512),
          n_tiles=100,
          level=0,
          seed=42,
          check_tissue=True,
          tissue_percent=80.0
      )
      
      # Preview locations
      random_tiler.locate_tiles(slide)
      
      # Extract tiles
      random_tiler.extract(slide)
      ```
      
      ## Workflow 2: Comprehensive Grid Extraction
      
      Complete tissue coverage for whole-slide analysis.
      
      ```python
      from histolab.slide import Slide
      from histolab.tiler import GridTiler
      from histolab.masks import TissueMask
      
      # Load slide
      slide = Slide("slide.svs", processed_path="output/grid_tiles/")
      
      # Use TissueMask for all tissue sections
      tissue_mask = TissueMask()
      slide.locate_mask(tissue_mask)
      
      # Configure grid tiler
      grid_tiler = GridTiler(
          tile_size=(512, 512),
          level=1,  # Use level 1 for faster extraction
          pixel_overlap=0,
          check_tissue=True,
          tissue_percent=70.0
      )
      
      # Preview grid
      grid_tiler.locate_tiles(slide)
      
      # Extract all tiles
      grid_tiler.extract(slide, extraction_mask=tissue_mask)
      ```
      
      ## Workflow 3: Quality-Driven Tile Selection
      
      Extract most informative tiles based on nuclei density.
      
      ```python
      from histolab.slide import Slide
      from histolab.tiler import ScoreTiler
      from histolab.scorer import NucleiScorer
      import pandas as pd
      import matplotlib.pyplot as plt
      
      # Load slide
      slide = Slide("slide.svs", processed_path="output/scored_tiles/")
      
      # Configure score tiler
      score_tiler = ScoreTiler(
          tile_size=(512, 512),
          n_tiles=50,
          level=0,
          scorer=NucleiScorer(),
          check_tissue=True
      )
      
      # Preview top tiles
      score_tiler.locate_tiles(slide)
      
      # Extract with report (CSV columns: filename, score, scaled_score)
      score_tiler.extract(slide, report_path="tiles_report.csv")
      
      # Analyze scores
      report_df = pd.read_csv("tiles_report.csv")
      plt.hist(report_df['score'], bins=20, edgecolor='black')
      plt.xlabel('Tile Score')
      plt.ylabel('Frequency')
      plt.title('Distribution of Tile Scores')
      plt.show()
      ```
      
      ## Workflow 4: Multi-Slide Processing Pipeline
      
      Process entire slide collection with consistent parameters.
      
      ```python
      from pathlib import Path
      from histolab.slide import Slide
      from histolab.tiler import RandomTiler
      import logging
      
      logging.basicConfig(level=logging.INFO)
      
      # Configure tiler once
      tiler = RandomTiler(
          tile_size=(512, 512),
          n_tiles=50,
          level=0,
          seed=42,
          check_tissue=True
      )
      
      # Process all slides
      slide_dir = Path("slides/")
      output_base = Path("output/")
      
      for slide_path in slide_dir.glob("*.svs"):
          print(f"\nProcessing: {slide_path.name}")
      
          # Create slide-specific output directory
          output_dir = output_base / slide_path.stem
          output_dir.mkdir(parents=True, exist_ok=True)
      
          # Load and process slide
          slide = Slide(slide_path, processed_path=output_dir)
      
          # Save thumbnail for review
          slide.thumbnail.save(output_dir / "thumbnail.png")
      
          # Extract tiles
          tiler.extract(slide)
      
          print(f"Completed: {slide_path.name}")
      ```
      
      ## Workflow 5: Custom Tissue Detection and Filtering
      
      Handle slides with artifacts, annotations, or unusual staining.
      
      ```python
      from histolab.slide import Slide
      from histolab.masks import TissueMask
      from histolab.tiler import RandomTiler
      from histolab.filters.image_filters import RgbToGrayscale, OtsuThreshold
      from histolab.filters.morphological_filters import (
          BinaryDilation, RemoveSmallObjects, RemoveSmallHoles
      )
      
      # Aggressive artifact removal: pass the individual filters as positional args.
      # TissueMask(*filters) overrides the default tissue-detection chain.
      custom_mask = TissueMask(
          RgbToGrayscale(),
          OtsuThreshold(),
          BinaryDilation(disk_size=10),
          RemoveSmallHoles(area_threshold=5000),
          RemoveSmallObjects(min_size=3000),  # Remove larger artifacts
      )
      
      # Load slide and visualize mask
      slide = Slide("slide.svs", processed_path="output/")
      slide.locate_mask(custom_mask)
      
      # Extract with custom mask (extraction_mask is an extract() arg, not a constructor arg)
      tiler = RandomTiler(tile_size=(512, 512), n_tiles=100, seed=42)
      tiler.extract(slide, extraction_mask=custom_mask)
      ```
      
  • SKILL.md 9.1 KB
    ---
    name: alterlab-histolab
    description: Extract and preprocess tiles from whole-slide images (WSI) with histolab — OpenSlide-backed slide loading, tissue detection and masks, Random/Grid/Score tile extraction, and image/morphological filters for H&E preprocessing. Use when the user needs lightweight WSI slide preprocessing — building tile datasets for ML training, tissue segmentation, or quick tile-based inspection of histopathology slides. For end-to-end computational-pathology, deep-learning model training, nucleus segmentation, or multiplexed/spatial-proteomics (CODEX, Vectra) pipelines prefer alterlab-pathml instead. Part of the AlterLab Academic Skills suite.
    license: Apache-2.0
    allowed-tools: Read Write Edit Bash(python:*) Bash(uv:*)
    compatibility: "Runs under `uv run python` with histolab 0.7.0 (still the latest release, Feb 2024) in a **dedicated environment**: it requires Python >= 3.8, < 3.12 and pins numpy <= 1.24.4, scipy < 1.10.1, scikit-image < 0.19.4 and openslide-python 1.3.1 — it will not co-install with a modern numpy 2.x / Python 3.12+ stack. The OpenSlide C library must also be present (`uv pip install openslide-bin`, or `brew install openslide` / `apt install libopenslide0`). No API key or account required."
    metadata:
        skill-author: AlterLab
        version: "1.1.0"
        last_updated: "2026-09-23"
    ---
    
    # Histolab
    
    ## Overview
    
    Histolab is a Python library for processing whole slide images (WSI) in digital pathology. It automates tissue detection, extracts informative tiles from gigapixel images, and prepares datasets for deep learning pipelines. The library handles multiple WSI formats, implements sophisticated tissue segmentation, and provides flexible tile extraction strategies.
    
    ## When to Use This Skill
    
    Use histolab for lightweight WSI tile pipelines: tissue detection, building tile datasets for ML training, H&E stain handling, and quick tile-based analysis of histopathology slides. For advanced spatial proteomics, multiplexed imaging, or full deep-learning pathology pipelines, use `alterlab-pathml` instead.
    
    ### Does NOT Trigger
    
    | Scenario | Use Instead |
    |----------|-------------|
    | End-to-end computational pathology, nucleus segmentation, multiplexed imaging (CODEX/Vectra) | `alterlab-pathml` |
    | Training the downstream classifier on the extracted tiles | `alterlab-pytorch-lightning` |
    | DICOM whole-slide or radiology images rather than SVS/NDPI | `alterlab-pydicom`, `alterlab-imaging-data-commons` |
    | Spatial transcriptomics on tissue sections | `alterlab-squidpy-spatial` |
    | Managing/versioning the slide collection itself | `alterlab-omero`, `alterlab-lamindb` |
    
    ## Installation
    
    ```bash
    # histolab 0.7.0 needs Python >= 3.8, < 3.12 and numpy 1.x — give it its own env
    uv venv --python 3.11 .venv-histolab
    uv pip install --python .venv-histolab "histolab==0.7.0" openslide-bin
    ```
    
    Two constraints that bite in a 2026 toolchain:
    
    1. **Pinned scientific stack.** 0.7.0 (Feb 2024, still the latest release) requires
       `numpy <= 1.24.4`, `scipy < 1.10.1`, `scikit-image < 0.19.4` and
       `openslide-python 1.3.1`. It cannot share an environment with a numpy 2.x / Python
       3.12+ project — resolve this with a separate venv rather than by loosening pins.
    2. **OpenSlide is a C library**, not bundled with the wheel. The simplest fix is the
       `openslide-bin` wheel (prebuilt binaries); otherwise `brew install openslide` on macOS
       or `apt install libopenslide0` on Debian/Ubuntu. Without it, `import histolab.slide`
       fails with `Couldn't locate OpenSlide dylib`.
    
    The examples below target histolab 0.7.0; the API differs in older releases.
    
    ## Core Workflow
    
    1. **Load** the slide with `Slide(path, processed_path=...)` and inspect dimensions/levels.
    2. **Detect tissue** with a mask (`TissueMask` or `BiggestTissueBoxMask`).
    3. **Preview** tile locations with `tiler.locate_tiles(slide)` before committing.
    4. **Extract** tiles with one of three tilers (Random/Grid/Score).
    
    Minimal example:
    
    ```python
    from histolab.slide import Slide
    from histolab.tiler import RandomTiler
    
    slide = Slide("slide.svs", processed_path="output/")
    # n_tiles, level, seed are CONSTRUCTOR args — not args to locate_tiles/extract.
    tiler = RandomTiler(tile_size=(512, 512), n_tiles=100, level=0, seed=42)
    tiler.locate_tiles(slide)   # preview locations on the thumbnail first
    tiler.extract(slide)        # writes PNGs into processed_path
    ```
    
    **API gotcha (histolab 0.7.0):** `locate_tiles()` and `extract()` take only
    `slide`, an optional `extraction_mask`, and logging/styling kwargs — they do
    **not** accept `n_tiles`. Set `n_tiles` (and `seed`, `level`, `tile_size`,
    `check_tissue`, `tissue_percent`) on the tiler constructor. The
    `extraction_mask` is passed to `extract()`/`locate_tiles()`, never to the
    constructor.
    
    Full copy-pasteable pipelines (quick start, 5 end-to-end workflows, and per-capability examples) live in `references/workflows.md`.
    
    ## Core Capabilities
    
    ### 1. Slide Management
    
    Load, inspect, and work with WSI files (SVS, TIFF, NDPI, etc.): access metadata
    (dimensions, magnification, properties), generate thumbnails, and work with
    pyramidal/multi-level structures. Key class: `Slide`.
    
    See `references/slide_management.md` for slide initialization, built-in sample
    datasets (`prostate_tissue`, `ovarian_tissue`, `breast_tissue`, `heart_tissue`,
    `aorta_tissue`, plus pen-marked and IHC samples), pyramid levels, and
    multi-slide processing.
    
    ### 2. Tissue Detection and Masks
    
    Automatically identify tissue regions and filter background/artifacts. Key
    classes: `TissueMask` (all tissue regions), `BiggestTissueBoxMask` (bounding box
    of largest region — the default), and `BinaryMask` (base class for custom masks).
    
    Choosing a mask:
    - `TissueMask`: multiple tissue sections, comprehensive analysis
    - `BiggestTissueBoxMask`: single main section, exclude artifacts (default)
    - Custom `BinaryMask`: specific ROI, exclude annotations, custom segmentation
    
    See `references/tissue_masks.md` for how detection filters work, visualizing
    masks with `locate_mask()`, and custom rectangular / annotation-exclusion masks.
    
    ### 3. Tile Extraction
    
    Extract smaller regions from large WSI using one of three strategies:
    
    - **RandomTiler** — fixed number of randomly positioned tiles. Best for sampling
      diverse regions, exploration, training data. Key params: `n_tiles`, `seed`.
    - **GridTiler** — systematic grid across tissue. Best for complete coverage,
      spatial analysis, reconstruction. Key param: `pixel_overlap`.
    - **ScoreTiler** — top-ranked tiles by scoring function. Best for informative
      regions, quality-driven selection. Key param: `scorer` (NucleiScorer,
      CellularityScorer, custom).
    
    Common parameters: `tile_size`, `level` (0 = highest res), `check_tissue`,
    `tissue_percent` (default 80%), `extraction_mask`. **Always preview with
    `locate_tiles()` before extracting.**
    
    See `references/tile_extraction.md` for scorers, reporting, and advanced
    (multi-level, hierarchical) extraction patterns.
    
    ### 4. Filters and Preprocessing
    
    Apply image-processing filters for tissue detection, QC, and preprocessing:
    - **Image filters** — `RgbToGrayscale`, `RgbToHsv`, `RgbToHed`, `OtsuThreshold`,
      `Invert`, `StretchContrast`, `HistogramEqualization`, `Lambda`.
    - **Morphological filters** — `BinaryDilation`, `BinaryErosion`, `BinaryOpening`,
      `BinaryClosing`, `RemoveSmallObjects`, `RemoveSmallHoles`.
    - **Composition** — `Compose` (in `histolab.filters.image_filters`) chains
      filters into pipelines. Pass custom filters to a mask as positional varargs:
      `TissueMask(RgbToGrayscale(), OtsuThreshold(), ...)`.
    
    See `references/filters_preprocessing.md` for filter chaining, common pipelines
    (tissue detection, pen removal, nuclei enhancement), and QC filters.
    
    ### 5. Visualization
    
    Display slides, masks, tile locations, and extraction quality: thumbnails, mask
    overlays via `locate_mask()`, tile-location previews via `locate_tiles()`, tile
    mosaics, and score distributions.
    
    See `references/visualization.md` for mosaics, quality-assessment plots,
    multi-slide comparison, and exporting high-resolution figures / PDF reports.
    
    ## Reference Index
    
    - `references/workflows.md` — quick start, per-capability examples, and 5
      end-to-end worked workflows (exploratory, grid, score-driven, multi-slide,
      custom tissue detection).
    - `references/slide_management.md` — loading/inspecting slides, sample datasets,
      pyramid levels, multi-slide processing.
    - `references/tissue_masks.md` — `TissueMask`/`BiggestTissueBoxMask`/`BinaryMask`,
      custom masks, mask visualization and integration.
    - `references/tile_extraction.md` — Random/Grid/Score tiler comparison, scorers,
      CSV reporting, advanced extraction patterns.
    - `references/filters_preprocessing.md` — image + morphological filters, filter
      composition, preprocessing pipelines, QC filters.
    - `references/visualization.md` — thumbnails, mask/tile previews, mosaics,
      quality plots, figure export.
    - `references/best_practices.md` — best practices, common use cases, and
      troubleshooting (no tiles, background tiles, slow extraction, artifacts).
    
    Load the specific reference file you need for detailed implementation guidance,
    troubleshooting, or advanced features.
    
    Part of the AlterLab Academic Skills suite.
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related