Claude Skill

alterlab-pathml

Run full computational-pathology workflows with PathML — whole-slide-image (WSI) analysis across 160+ slide formats, multiplexed immunofluorescence (CODEX, Vectra, MERFISH), nucleus segmentation/classification (HoVer-Net, HACTNet), tissue- and cell-graph construction, HDF5 datase

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-pathml-e4836c0.zip · 39 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-pathml
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

PathML

Overview

PathML is a comprehensive Python toolkit for computational pathology workflows, designed to facilitate machine learning and image analysis for whole-slide pathology images. The framework provides modular, composable tools for loading diverse slide formats, preprocessing images, constructing spatial graphs, training deep learning models, and analyzing multiparametric imaging data from technologies like CODEX and multiplex immunofluorescence.

When to Use This Skill

Apply this skill for:

  • Loading and processing whole-slide images (WSI) in various proprietary formats
  • Preprocessing H&E stained tissue images with stain normalization
  • Nucleus detection, segmentation, and classification workflows
  • Building cell and tissue graphs for spatial analysis
  • Training or deploying machine learning models (HoVer-Net, HACTNet) on pathology data
  • Analyzing multiparametric imaging (CODEX, Vectra, MERFISH) for spatial proteomics
  • Quantifying marker expression from multiplex immunofluorescence
  • Managing large-scale pathology datasets with HDF5 storage
  • Tile-based analysis and stitching operations

Does NOT Trigger

Scenario Use Instead
Lightweight H&E preprocessing, tissue masking, Random/Grid/Score tile extraction alterlab-histolab
Spatial transcriptomics neighborhood stats on Visium/Xenium/MERFISH tables alterlab-squidpy-spatial
Single-cell expression analysis of the resulting cell x marker matrix alterlab-scanpy
Training a general vision model with no pathology-specific I/O or transforms alterlab-pytorch-lightning
Graph neural networks on a graph you already built alterlab-torch-geometric

Core Capabilities

PathML provides six major capability areas documented in detail within reference files:

1. Image Loading & Formats

Load whole-slide images from 160+ proprietary formats including Aperio SVS, Hamamatsu NDPI, Leica SCN, Zeiss ZVI, DICOM, and OME-TIFF. PathML automatically handles vendor-specific formats and provides unified interfaces for accessing image pyramids, metadata, and regions of interest.

See: references/image_loading.md for supported formats, loading strategies, and working with different slide types.

2. Preprocessing Pipelines

Build modular preprocessing pipelines by composing transforms for image manipulation, quality control, stain normalization, tissue detection, and mask operations. PathML's Pipeline architecture enables reproducible, scalable preprocessing across large datasets.

Key transforms:

  • StainNormalizationHE - Macenko/Vahadane stain normalization
  • TissueDetectionHE, NucleusDetectionHE - Tissue/nucleus segmentation
  • MedianBlur, GaussianBlur - Noise reduction
  • LabelArtifactTileHE - Quality control for artifacts

See: references/preprocessing.md for complete transform catalog, pipeline construction, and preprocessing workflows.

3. Graph Construction

Construct spatial graphs representing cellular and tissue-level relationships. Extract features from segmented objects to create graph-based representations suitable for graph neural networks and spatial analysis.

See: references/graphs.md for graph construction methods, feature extraction, and spatial analysis workflows.

4. Machine Learning

Train and deploy deep learning models for nucleus detection, segmentation, and classification. PathML integrates PyTorch with pre-built models (HoVer-Net, HACTNet), custom DataLoaders, and ONNX support for inference.

Key models:

  • HoVer-Net - Simultaneous nucleus segmentation and classification
  • HACTNet - Hierarchical cell-type classification

See: references/machine_learning.md for model training, evaluation, inference workflows, and working with public datasets.

5. Multiparametric Imaging

Analyze spatial proteomics and gene expression data from CODEX, Vectra, MERFISH, and other multiplex imaging platforms. PathML provides specialized slide classes and transforms for processing multiparametric data, cell segmentation with Mesmer, and quantification workflows.

See: references/multiparametric.md for CODEX/Vectra workflows, cell segmentation, marker quantification, and integration with AnnData.

6. Data Management

Efficiently store and manage large pathology datasets using HDF5 format. PathML handles tiles, masks, metadata, and extracted features in unified storage structures optimized for machine learning workflows.

See: references/data_management.md for HDF5 integration, tile management, dataset organization, and batch processing strategies.

Quick Start

Installation

PathML needs native dependencies present first — OpenSlide and a JDK (Bio-Formats is driven through JPype/javabridge) — so the maintainers recommend a conda environment; pure-pip installs commonly fail on those native deps.

Give PathML its own environment, because 3.0.8 pins much of the scientific stack to exact or upper-bounded versions:

Pinned by PathML 3.0.8 Current elsewhere
numpy<2 2.5.x
pandas<=2.1.4 3.0.x
scanpy==1.9.6, anndata<=0.10.3 scanpy 1.12.x, anndata 0.13.x
scikit-image<=0.22.0, networkx<=3.2.1, h5py==3.10.0 all newer
torch==2.12.0, torch-geometric==2.8.0 torch 2.14.x

Installing PathML next to alterlab-scanpy or alterlab-squidpy-spatial will either fail to resolve or silently downgrade those skills' stack. Move results between environments as files (HDF5/AnnData written by PathML, read by a current-scanpy env) rather than trying to satisfy both pin sets.

# In a dedicated env, with OpenSlide and a JDK already installed.
uv venv .venv-pathml && source .venv-pathml/bin/activate
uv pip install pathml

Basic Workflow Example

from pathml.core import HESlide
from pathml.preprocessing import Pipeline, StainNormalizationHE, TissueDetectionHE

# Load a whole-slide image. Use the HESlide convenience class for H&E,
# or SlideData(filepath=..., slide_type=types.HE) for the generic constructor.
# (There is no SlideData.from_slide.)
wsi = HESlide("path/to/slide.svs", name="example")

# Create preprocessing pipeline
pipeline = Pipeline([
    TissueDetectionHE(),
    StainNormalizationHE(target="normalize", stain_estimation_method="macenko"),
])

# Run the pipeline on the slide (SlideData.run handles tiling + transforms)
wsi.run(pipeline)

# Access processed tiles
for tile in wsi.tiles:
    processed_image = tile.image
    tissue_mask = tile.masks["tissue"]

Common Workflows

H&E Image Analysis:

  1. Load WSI with appropriate slide class
  2. Apply tissue detection and stain normalization
  3. Perform nucleus detection or train segmentation models
  4. Extract features and build spatial graphs
  5. Conduct downstream analysis

Multiparametric Imaging (CODEX):

  1. Load CODEX slide with CODEXSlide
  2. Collapse multi-run channel data with CollapseRunsCODEX
  3. Segment cells using SegmentMIF (Mesmer)
  4. Quantify per-cell marker expression with QuantifyMIF
  5. Read the resulting AnnData from slide.counts for single-cell analysis

Training ML Models:

  1. Prepare data with a pathml.datasets DataModule (e.g. PanNukeDataModule) or a TileDataset
  2. Train HoVerNet (or another model) with a standard PyTorch loop
  3. Post-process predictions with post_process_batch_hovernet
  4. Evaluate on held-out test sets
  5. Optionally export to ONNX for inference

Reference Files

Load the relevant reference for detailed API, workflows, and gotchas:

  • references/image_loading.md - WSI formats, slide classes, loading strategies
  • references/preprocessing.md - transform catalog, pipeline construction, stain normalization
  • references/graphs.md - graph builders, feature extraction, spatial analysis
  • references/machine_learning.md - HoVer-Net/HACTNet, training, datasets, ONNX inference
  • references/multiparametric.md - CODEX/Vectra/multiplex IF, cell segmentation, quantification
  • references/data_management.md - h5path storage, tile management, batch processing

PathML's API surface shifts between releases; treat the reference code as workflow scaffolding and confirm exact class/method names against the version you have installed (python -c "import pathml; print(pathml.__version__)") and the official API docs at https://pathml.readthedocs.io/.

Files (alterlab-academic-skills)
  • evals
    • evals.json 4.2 KB
      {
        "skill": "alterlab-pathml",
        "evals": [
          {
            "id": "he-preprocessing-pipeline",
            "prompt": "I have an Aperio SVS whole-slide image of H&E tissue. Build me a PathML preprocessing pipeline that does tissue detection and Macenko stain normalization, then iterate the processed tiles.",
            "expected_output": "Invokes alterlab-pathml. Loads the slide with HESlide(...) (or SlideData(filepath=..., slide_type=types.HE); NOT a from_slide factory), builds a Pipeline([TissueDetectionHE(), StainNormalizationHE(stain_estimation_method='macenko')]), runs it via wsi.run(pipeline), and iterates wsi.tiles accessing tile.image and tile.masks. Composable WSI preprocessing with stain normalization is a core PathML capability.",
            "assertions": [
              { "type": "should_trigger", "value": true },
              { "type": "output_contains", "value": "StainNormalizationHE" }
            ]
          },
          {
            "id": "nucleus-segmentation-hovernet",
            "prompt": "I want to run simultaneous nucleus segmentation and classification on my pathology tiles using a pretrained model. Which PathML model do I use and how do I set up the workflow?",
            "expected_output": "Invokes alterlab-pathml. Recommends HoVer-Net for simultaneous nucleus segmentation and classification, using PathML's PyTorch integration and DataLoaders, with optional ONNX export for inference. Deep-learning nucleus detection/segmentation is an in-scope PathML ML workflow.",
            "assertions": [
              { "type": "should_trigger", "value": true },
              { "type": "output_contains", "value": "HoVer-Net" }
            ]
          },
          {
            "id": "codex-multiplex-quantification",
            "prompt": "I have a multiplexed CODEX run. I need to collapse the channel data, segment cells, quantify marker expression, and export everything to an AnnData object for single-cell analysis.",
            "expected_output": "Invokes alterlab-pathml. Loads the CODEXSlide, collapses multi-run channel data, segments cells with the Mesmer model, quantifies per-cell marker expression, and exports to AnnData. Multiparametric/spatial-proteomics imaging (CODEX, Vectra, MERFISH) is a documented PathML capability.",
            "assertions": [
              { "type": "should_trigger", "value": true },
              { "type": "output_contains", "value": "CODEX" }
            ]
          },
          {
            "id": "tissue-graph-construction",
            "prompt": "After segmenting cells on my H&E slides I want to build a spatial cell graph with extracted node features so I can train a graph neural network like HACTNet.",
            "expected_output": "Invokes alterlab-pathml. Constructs cell/tissue graphs from segmented objects, extracts node features, and prepares graph representations suitable for graph neural networks (e.g. HACTNet). Spatial graph construction for GNNs is an in-scope PathML capability.",
            "assertions": [
              { "type": "should_trigger", "value": true },
              { "type": "output_contains", "value": "graph" }
            ]
          },
          {
            "id": "near-miss-histolab",
            "prompt": "I just need to extract a grid of H&E tiles from a single SVS slide at 20x and save them as PNGs — no segmentation, no normalization, no ML.",
            "expected_output": "Does NOT invoke this skill; defers to alterlab-histolab. The ask is simple H&E tile extraction with no deep-learning pipeline, multiplexed imaging, or graph construction. PathML is the heavier framework; for plain tiling histolab is the lighter tool, as the description states.",
            "assertions": [
              { "type": "should_not_trigger", "value": true },
              { "type": "output_contains", "value": "alterlab-histolab" }
            ]
          },
          {
            "id": "near-miss-scanpy",
            "prompt": "I already have a cells-by-genes AnnData matrix from my CODEX quantification. Cluster the cells with Leiden, run UMAP, and find marker genes per cluster.",
            "expected_output": "Does NOT invoke this skill; defers to alterlab-scanpy. The ask is downstream single-cell clustering/embedding/marker analysis on an existing AnnData matrix, not slide loading, segmentation, or image preprocessing. PathML produces the AnnData; scanpy analyzes it.",
            "assertions": [
              { "type": "should_not_trigger", "value": true },
              { "type": "output_contains", "value": "alterlab-scanpy" }
            ]
          }
        ]
      }
      
  • references
    • data_management.md 17.7 KB
      # Data Management & Storage
      
      ## Overview
      
      PathML provides efficient data management solutions for handling large-scale pathology datasets through HDF5 storage, tile management strategies, and optimized batch processing workflows. The framework enables seamless storage and retrieval of images, masks, features, and metadata in formats optimized for machine learning pipelines and downstream analysis.
      
      ## h5path Storage
      
      PathML persists processed slides in its own **h5path** format (an HDF5 layout). The relevant calls are `SlideData.write(path)` and `SlideDataset.write(dir)` — there is no `to_hdf5`/`from_hdf5` method. h5path gives you compression, chunked storage, fast random access, and a hierarchical layout for images/masks/tiles/counts.
      
      ### Writing h5path
      
      **Single slide:**
      ```python
      from pathml.core import HESlide
      
      # Load and process slide (run() handles tiling)
      wsi = HESlide("slide.svs")
      wsi.run(pipeline, tile_size=256, level=1)
      
      # Write to disk (h5path); use the .h5path extension by convention
      wsi.write("processed_slide.h5path")
      ```
      
      **Multiple slides (SlideDataset):**
      ```python
      from pathml.core import HESlide, SlideDataset
      from dask.distributed import Client
      import glob
      
      slide_paths = glob.glob("data/*.svs")
      dataset = SlideDataset([HESlide(p) for p in slide_paths])
      
      client = Client(n_workers=8, threads_per_worker=2)
      dataset.run(pipeline, client=client, tile_size=256, level=1)
      
      # Writes one h5path file per slide into the directory
      dataset.write("processed/")
      ```
      
      ### h5path File Structure
      
      h5path files are organized hierarchically. The layout below is illustrative — the exact group names are internal and version-dependent, so read/write through the PathML API rather than hard-coding paths:
      
      ```
      processed_dataset.h5path
      ├── slide_0/
      │   ├── metadata/
      │   │   ├── name
      │   │   ├── level
      │   │   ├── dimensions
      │   │   └── ...
      │   ├── tiles/
      │   │   ├── tile_0/
      │   │   │   ├── image  (H, W, C) array
      │   │   │   ├── coords  (x, y)
      │   │   │   └── masks/
      │   │   │       ├── tissue
      │   │   │       ├── nucleus
      │   │   │       └── ...
      │   │   ├── tile_1/
      │   │   └── ...
      │   └── features/
      │       ├── tile_features  (n_tiles, n_features)
      │       └── feature_names
      ├── slide_1/
      └── ...
      ```
      
      ### Reading h5path back
      
      **Reload a written slide** by constructing `SlideData` from the h5path file. To serve its tiles to a model, wrap it in `pathml.datasets.TileDataset`:
      
      ```python
      from pathml.core import SlideData
      from pathml.datasets import TileDataset
      
      # Reload the processed slide
      wsi = SlideData("processed_slide.h5path")
      
      # Access tiles
      for tile in wsi.tiles:
          image = tile.image
          masks = tile.masks
      
      # Or stream tiles to a PyTorch DataLoader
      tile_dataset = TileDataset("processed_slide.h5path")
      ```
      
      (If your PathML version exposes a different reload entry point, confirm it in the core API docs.)
      
      **Low-level access with h5py** is possible since h5path is HDF5 underneath, but the internal group layout is an implementation detail and can change between versions — prefer the PathML API for forward compatibility:
      
      ```python
      import h5py
      
      with h5py.File("processed_slide.h5path", "r") as f:
          print(list(f.keys()))  # inspect the layout for your version
      ```
      
      ## Tile Management
      
      ### Tile Generation Strategies
      
      Tiling is driven by `generate_tiles` (a generator) or by the tiling arguments passed to `SlideData.run`/`SlideDataset.run`. Confirm the tile-size keyword (`shape` vs `tile_size`) for your installed version.
      
      **Fixed-size tiles with no overlap** (`stride == shape`):
      ```python
      for tile in wsi.generate_tiles(level=1, shape=256, stride=256, pad=False):
          ...
      ```
      - Standard tile-based processing/classification; simple, no redundancy; edge effects at boundaries.
      
      **Overlapping tiles** (`stride < shape`):
      ```python
      for tile in wsi.generate_tiles(level=1, shape=256, stride=128):  # 50% overlap
          ...
      ```
      - Better for segmentation/detection (reduces boundary artifacts); more tiles, redundant compute.
      
      **Tissue-only tiling:** run a pipeline that includes `TissueDetectionHE`, then filter the resulting `wsi.tiles` by tissue coverage:
      ```python
      wsi.run(pipeline, tile_size=256, level=1)  # pipeline includes TissueDetectionHE()
      
      tile_area = 256 * 256
      tissue_tiles = [
          tile for tile in wsi.tiles
          if tile.masks.get("tissue") is not None
          and tile.masks["tissue"].sum() / tile_area > 0.5
      ]
      ```
      
      ### Tile Stitching
      
      PathML does not ship a general `stitch_tiles` utility; reassemble predictions yourself from each tile's `coords`. A simple non-overlapping reassembly:
      
      ```python
      import numpy as np
      
      # Allocate at the working level's dimensions (W, H from the backend)
      W, H = wsi.shape
      canvas = np.zeros((H, W), dtype=np.float32)
      
      for tile in wsi.tiles:
          pred = model.predict(tile.image)  # (h, w)
          j, i = tile.coords                # top-left (x, y)
          h, w = pred.shape
          canvas[i:i + h, j:j + w] = pred   # average/blend instead for overlaps
      ```
      
      For overlapping tiles, accumulate predictions and a per-pixel count, then divide to average.
      
      ## Dataset Organization
      
      ### Directory Structure for Large Projects
      
      Organize pathology projects with consistent structure:
      
      ```
      project/
      ├── raw_slides/
      │   ├── cohort1/
      │   │   ├── slide001.svs
      │   │   ├── slide002.svs
      │   │   └── ...
      │   └── cohort2/
      │       └── ...
      ├── processed/
      │   ├── cohort1/
      │   │   ├── slide001.h5
      │   │   ├── slide002.h5
      │   │   └── ...
      │   └── cohort2/
      │       └── ...
      ├── features/
      │   ├── cohort1_features.h5
      │   └── cohort2_features.h5
      ├── models/
      │   ├── hovernet_checkpoint.pth
      │   └── classifier.onnx
      ├── results/
      │   ├── predictions/
      │   ├── visualizations/
      │   └── metrics.csv
      └── metadata/
          ├── clinical_data.csv
          └── slide_manifest.csv
      ```
      
      ### Metadata Management
      
      Store slide-level and cohort-level metadata:
      
      ```python
      import pandas as pd
      
      # Slide manifest
      manifest = pd.DataFrame({
          'slide_id': ['slide001', 'slide002', 'slide003'],
          'path': ['raw_slides/cohort1/slide001.svs', ...],
          'cohort': ['cohort1', 'cohort1', 'cohort2'],
          'tissue_type': ['breast', 'breast', 'lung'],
          'scanner': ['Aperio', 'Hamamatsu', 'Aperio'],
          'magnification': [40, 40, 20],
          'staining': ['H&E', 'H&E', 'H&E']
      })
      
      manifest.to_csv('metadata/slide_manifest.csv', index=False)
      
      # Clinical data
      clinical = pd.DataFrame({
          'slide_id': ['slide001', 'slide002', 'slide003'],
          'patient_id': ['P001', 'P002', 'P003'],
          'age': [55, 62, 48],
          'diagnosis': ['invasive', 'in_situ', 'invasive'],
          'stage': ['II', 'I', 'III'],
          'outcome': ['favorable', 'favorable', 'poor']
      })
      
      clinical.to_csv('metadata/clinical_data.csv', index=False)
      
      # Load and merge
      manifest = pd.read_csv('metadata/slide_manifest.csv')
      clinical = pd.read_csv('metadata/clinical_data.csv')
      data = manifest.merge(clinical, on='slide_id')
      ```
      
      ## Batch Processing Strategies
      
      ### Sequential Processing
      
      Process slides one at a time (memory-efficient):
      
      ```python
      import glob
      from pathml.core import HESlide
      from pathml.preprocessing import Pipeline
      
      slide_paths = glob.glob("raw_slides/**/*.svs", recursive=True)
      
      for slide_path in slide_paths:
          wsi = HESlide(slide_path)
          wsi.run(pipeline, tile_size=256, level=1)
      
          output_path = slide_path.replace("raw_slides", "processed").replace(".svs", ".h5path")
          wsi.write(output_path)
          print(f"Processed: {slide_path}")
      ```
      
      ### Parallel Processing with Dask
      
      Process multiple slides in parallel:
      
      ```python
      from pathml.core import HESlide, SlideDataset
      from dask.distributed import Client, LocalCluster
      from pathml.preprocessing import Pipeline
      
      # Start Dask cluster
      cluster = LocalCluster(
          n_workers=8,
          threads_per_worker=2,
          memory_limit="8GB",
          dashboard_address=":8787",  # progress at localhost:8787
      )
      client = Client(cluster)
      
      # Create dataset from slide objects
      slide_paths = glob.glob("raw_slides/**/*.svs", recursive=True)
      dataset = SlideDataset([HESlide(p) for p in slide_paths])
      
      # Distribute processing (pass the client; tiling options go to run())
      dataset.run(pipeline, client=client, tile_size=256, level=1)
      
      # Write one h5path per slide into a directory
      dataset.write("processed/")
      
      client.close()
      cluster.close()
      ```
      
      ### Batch Processing with Job Arrays
      
      For HPC clusters (SLURM, PBS):
      
      ```python
      # submit_jobs.py
      import os
      import glob
      
      slide_paths = glob.glob('raw_slides/**/*.svs', recursive=True)
      
      # Write slide list
      with open('slide_list.txt', 'w') as f:
          for path in slide_paths:
              f.write(path + '\n')
      
      # Create SLURM job script
      slurm_script = """#!/bin/bash
      #SBATCH --array=1-{n_slides}
      #SBATCH --cpus-per-task=4
      #SBATCH --mem=16G
      #SBATCH --time=4:00:00
      #SBATCH --output=logs/slide_%A_%a.out
      
      # Get slide path for this array task
      SLIDE_PATH=$(sed -n "${{SLURM_ARRAY_TASK_ID}}p" slide_list.txt)
      
      # Run processing
      python process_slide.py --slide_path $SLIDE_PATH
      """.format(n_slides=len(slide_paths))
      
      with open('submit_jobs.sh', 'w') as f:
          f.write(slurm_script)
      
      # Submit: sbatch submit_jobs.sh
      ```
      
      ```python
      # process_slide.py
      import argparse
      from pathml.core import HESlide
      from pathml.preprocessing import Pipeline
      
      parser = argparse.ArgumentParser()
      parser.add_argument("--slide_path", type=str, required=True)
      args = parser.parse_args()
      
      # Load and process
      wsi = HESlide(args.slide_path)
      
      pipeline = Pipeline([...])
      wsi.run(pipeline, tile_size=256, level=1)
      
      # Save
      output_path = args.slide_path.replace("raw_slides", "processed").replace(".svs", ".h5path")
      wsi.write(output_path)
      
      print(f"Processed: {args.slide_path}")
      ```
      
      ## Feature Extraction and Storage
      
      ### Extracting Features
      
      ```python
      from pathml.core import SlideData
      import torch
      import numpy as np
      
      # Load pre-trained model for feature extraction
      model = torch.load('models/feature_extractor.pth')
      model.eval()
      device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
      model = model.to(device)
      
      # Load processed slide from h5path
      wsi = SlideData("processed/slide001.h5path")
      
      # Extract features for each tile
      features = []
      coords = []
      
      for tile in wsi.tiles:
          # Preprocess tile
          tile_tensor = torch.from_numpy(tile.image).permute(2, 0, 1).unsqueeze(0).float()
          tile_tensor = tile_tensor.to(device)
      
          # Extract features
          with torch.no_grad():
              feature_vec = model(tile_tensor).cpu().numpy().flatten()
      
          features.append(feature_vec)
          coords.append(tile.coords)
      
      features = np.array(features)  # Shape: (n_tiles, feature_dim)
      coords = np.array(coords)  # Shape: (n_tiles, 2)
      ```
      
      ### Storing Features in HDF5
      
      ```python
      import h5py
      
      # Save features
      with h5py.File('features/slide001_features.h5', 'w') as f:
          f.create_dataset('features', data=features, compression='gzip')
          f.create_dataset('coords', data=coords)
          f.attrs['feature_dim'] = features.shape[1]
          f.attrs['num_tiles'] = features.shape[0]
          f.attrs['model'] = 'resnet50'
      
      # Load features
      with h5py.File('features/slide001_features.h5', 'r') as f:
          features = f['features'][:]
          coords = f['coords'][:]
          feature_dim = f.attrs['feature_dim']
      ```
      
      ### Feature Database for Multiple Slides
      
      ```python
      # Create consolidated feature database
      import h5py
      import glob
      
      feature_files = glob.glob('features/*_features.h5')
      
      with h5py.File('features/all_features.h5', 'w') as out_f:
          for i, feature_file in enumerate(feature_files):
              slide_name = feature_file.split('/')[-1].replace('_features.h5', '')
      
              with h5py.File(feature_file, 'r') as in_f:
                  features = in_f['features'][:]
                  coords = in_f['coords'][:]
      
                  # Store in consolidated file
                  grp = out_f.create_group(f'slide_{i}')
                  grp.create_dataset('features', data=features, compression='gzip')
                  grp.create_dataset('coords', data=coords)
                  grp.attrs['slide_name'] = slide_name
      
      # Query features from all slides
      with h5py.File('features/all_features.h5', 'r') as f:
          for slide_key in f.keys():
              slide_name = f[slide_key].attrs['slide_name']
              features = f[f'{slide_key}/features'][:]
              # Process...
      ```
      
      ## Data Versioning
      
      ### Version Control with DVC
      
      Use Data Version Control (DVC) for large dataset management:
      
      ```bash
      # Initialize DVC
      dvc init
      
      # Add data directory
      dvc add raw_slides/
      dvc add processed/
      
      # Commit to git
      git add raw_slides.dvc processed.dvc .gitignore
      git commit -m "Add raw and processed slides"
      
      # Push data to remote storage (S3, GCS, etc.)
      dvc remote add -d storage s3://my-bucket/pathml-data
      dvc push
      
      # Pull data on another machine
      git pull
      dvc pull
      ```
      
      ### Checksums and Validation
      
      Validate data integrity:
      
      ```python
      import hashlib
      import pandas as pd
      
      def compute_checksum(file_path):
          """Compute MD5 checksum of file."""
          hash_md5 = hashlib.md5()
          with open(file_path, 'rb') as f:
              for chunk in iter(lambda: f.read(4096), b""):
                  hash_md5.update(chunk)
          return hash_md5.hexdigest()
      
      # Create checksum manifest
      slide_paths = glob.glob('raw_slides/**/*.svs', recursive=True)
      checksums = []
      
      for slide_path in slide_paths:
          checksum = compute_checksum(slide_path)
          checksums.append({
              'path': slide_path,
              'checksum': checksum,
              'size_mb': os.path.getsize(slide_path) / 1e6
          })
      
      checksum_df = pd.DataFrame(checksums)
      checksum_df.to_csv('metadata/checksums.csv', index=False)
      
      # Validate files
      def validate_files(manifest_path):
          manifest = pd.read_csv(manifest_path)
          for _, row in manifest.iterrows():
              current_checksum = compute_checksum(row['path'])
              if current_checksum != row['checksum']:
                  print(f"ERROR: Checksum mismatch for {row['path']}")
              else:
                  print(f"OK: {row['path']}")
      
      validate_files('metadata/checksums.csv')
      ```
      
      ## Performance Optimization
      
      ### Compression Settings
      
      Optimize HDF5 compression for speed vs. size:
      
      ```python
      import h5py
      
      # Fast compression (less CPU, larger files)
      with h5py.File('output.h5', 'w') as f:
          f.create_dataset(
              'images',
              data=images,
              compression='gzip',
              compression_opts=1  # Level 1-9, lower = faster
          )
      
      # Maximum compression (more CPU, smaller files)
      with h5py.File('output.h5', 'w') as f:
          f.create_dataset(
              'images',
              data=images,
              compression='gzip',
              compression_opts=9
          )
      
      # Balanced (recommended)
      with h5py.File('output.h5', 'w') as f:
          f.create_dataset(
              'images',
              data=images,
              compression='gzip',
              compression_opts=4,
              chunks=True  # Enable chunking for better I/O
          )
      ```
      
      ### Chunking Strategy
      
      Optimize chunked storage for access patterns:
      
      ```python
      # For tile-based access (access one tile at a time)
      with h5py.File('tiles.h5', 'w') as f:
          f.create_dataset(
              'tiles',
              shape=(n_tiles, 256, 256, 3),
              dtype='uint8',
              chunks=(1, 256, 256, 3),  # One tile per chunk
              compression='gzip'
          )
      
      # For channel-based access (access all tiles for one channel)
      with h5py.File('tiles.h5', 'w') as f:
          f.create_dataset(
              'tiles',
              shape=(n_tiles, 256, 256, 3),
              dtype='uint8',
              chunks=(n_tiles, 256, 256, 1),  # All tiles for one channel
              compression='gzip'
          )
      ```
      
      ### Memory-Mapped Arrays
      
      Use memory mapping for large arrays:
      
      ```python
      import numpy as np
      
      # Save as memory-mapped file
      features_mmap = np.memmap(
          'features/features.mmap',
          dtype='float32',
          mode='w+',
          shape=(n_tiles, feature_dim)
      )
      
      # Populate
      for i, tile in enumerate(wsi.tiles):
          features_mmap[i] = extract_features(tile)
      
      # Flush to disk
      features_mmap.flush()
      
      # Load without reading into memory
      features_mmap = np.memmap(
          'features/features.mmap',
          dtype='float32',
          mode='r',
          shape=(n_tiles, feature_dim)
      )
      
      # Access subset efficiently
      subset = features_mmap[1000:2000]  # Only loads requested rows
      ```
      
      ## Best Practices
      
      1. **Use HDF5 for processed data:** Save preprocessed tiles and features to HDF5 for fast access
      
      2. **Separate raw and processed data:** Keep original slides separate from processed outputs
      
      3. **Maintain metadata:** Track slide provenance, processing parameters, and clinical annotations
      
      4. **Implement checksums:** Validate data integrity, especially after transfers
      
      5. **Version datasets:** Use DVC or similar tools to version large datasets
      
      6. **Optimize storage:** Balance compression level with I/O performance
      
      7. **Organize by cohort:** Structure directories by study cohort for clarity
      
      8. **Regular backups:** Back up both data and metadata to remote storage
      
      9. **Document processing:** Keep logs of processing steps, parameters, and versions
      
      10. **Monitor disk usage:** Track storage consumption as datasets grow
      
      ## Common Issues and Solutions
      
      **Issue: HDF5 files very large**
      - Increase compression level: `compression_opts=9`
      - Store only necessary data (avoid redundant copies)
      - Use appropriate data types (uint8 for images vs. float64)
      
      **Issue: Slow HDF5 read/write**
      - Optimize chunk size for access pattern
      - Reduce compression level for faster I/O
      - Use SSD storage instead of HDD
      - Enable parallel HDF5 with MPI
      
      **Issue: Running out of disk space**
      - Delete intermediate files after processing
      - Compress inactive datasets
      - Move old data to archival storage
      - Use cloud storage for less-accessed data
      
      **Issue: Data corruption or loss**
      - Implement regular backups
      - Use RAID for redundancy
      - Validate checksums after transfers
      - Use version control (DVC)
      
      ## Additional Resources
      
      - **HDF5 Documentation:** https://www.hdfgroup.org/solutions/hdf5/
      - **h5py:** https://docs.h5py.org/
      - **DVC (Data Version Control):** https://dvc.org/
      - **Dask:** https://docs.dask.org/
      - **PathML Data Management API:** https://pathml.readthedocs.io/en/latest/api_datasets_reference.html
      
    • graphs.md 11.4 KB
      # Graph Construction & Spatial Analysis
      
      ## Overview
      
      PathML provides tools for constructing spatial graphs from tissue images to represent cellular and tissue-level relationships. Graph-based representations enable sophisticated spatial analysis, including neighborhood analysis, cell-cell interaction studies, and graph neural network applications. These graphs capture both morphological features and spatial topology for downstream computational analysis.
      
      ## Graph Types
      
      PathML supports construction of multiple graph types:
      
      ### Cell Graphs
      - Nodes represent individual cells
      - Edges represent spatial proximity or biological interactions
      - Node features include morphology, marker expression, cell type
      - Suitable for single-cell spatial analysis
      
      ### Tissue Graphs
      - Nodes represent tissue regions or superpixels
      - Edges represent spatial adjacency
      - Node features include tissue composition, texture features
      - Suitable for tissue-level spatial patterns
      
      ### Spatial Transcriptomics Graphs
      - Nodes represent spatial spots or cells
      - Edges encode spatial relationships
      - Node features include gene expression profiles
      - Suitable for spatial omics analysis
      
      ## Graph Construction Workflow
      
      PathML's graph module exposes builder classes (subclasses of `BaseGraphBuilder`) rather than a single `CellGraph` factory: `KNNGraphBuilder`, `RAGGraphBuilder` (region-adjacency, for superpixel/tissue graphs), and `MSTGraphBuilder` (minimum spanning tree). Tissue regions for tissue graphs come from the superpixel extractors (`SLICSuperpixelExtractor`, `ColorMergedSuperpixelExtractor`, etc.), and per-node features come from `GraphFeatureExtractor`. Verify class names and call signatures against the API docs for your installed version.
      
      ### From Segmentation to Graphs
      
      Convert a cell instance-segmentation mask into a spatial cell graph with a builder. The builder takes the per-cell centroids and a feature matrix and returns a `pathml.graph.utils.Graph` (PyG-compatible) object.
      
      ```python
      import numpy as np
      from skimage.measure import regionprops
      from pathml.graph import KNNGraphBuilder
      from pathml.preprocessing import Pipeline, SegmentMIF
      
      # 1. Segment cells
      pipeline = Pipeline([
          SegmentMIF(model="mesmer", nuclear_channel=0, cytoplasm_channel=29),
      ])
      slide.run(pipeline)
      
      # 2. Get the instance mask and per-cell centroids + features
      inst_map = slide.masks["cell_segmentation"]
      props = regionprops(inst_map)
      centroids = np.array([p.centroid[::-1] for p in props])  # (x, y) per cell
      features = np.array([[p.area, p.eccentricity, p.solidity] for p in props])
      
      # 3. Build a k-NN cell graph
      builder = KNNGraphBuilder(k=5, thresh=50)  # k neighbors within a distance threshold
      graph = builder.process(inst_map, features=features, centroids=centroids)
      
      # 4. Access components (Graph is a torch_geometric-style object)
      x = graph.node_features      # node feature matrix
      edge_index = graph.edge_index  # (2, n_edges) connectivity
      ```
      
      ### Builder Choices
      
      - `KNNGraphBuilder(k=..., thresh=...)` - connect each node to its k nearest neighbors within an optional distance threshold; good for cell graphs.
      - `RAGGraphBuilder` - region-adjacency graph; connects touching regions, suited to superpixel/tissue graphs.
      - `MSTGraphBuilder` - minimum spanning tree over node positions; a sparse, fully connected backbone.
      
      For tissue graphs, first extract superpixels (e.g. `SLICSuperpixelExtractor` or `ColorMergedSuperpixelExtractor`), then pass the label map to `RAGGraphBuilder`.
      
      ## Node Features
      
      PathML's `GraphFeatureExtractor` computes per-region features from an instance/label map; you can also compute features yourself with scikit-image and pass them to the builder as the `features` matrix.
      
      ### Morphological and Intensity Features (scikit-image)
      
      `skimage.measure.regionprops` / `regionprops_table` covers the common morphology and per-channel intensity statistics:
      
      ```python
      import numpy as np
      from skimage.measure import regionprops_table
      
      # Morphology
      morph = regionprops_table(
          inst_map,
          properties=["area", "perimeter", "eccentricity", "solidity",
                      "axis_major_length", "axis_minor_length", "orientation"],
      )
      
      # Per-channel mean intensity (image shape (H, W, C))
      intensity = regionprops_table(
          inst_map,
          intensity_image=multichannel_image,
          properties=["intensity_mean"],
      )
      
      # Stack into a node feature matrix aligned with the builder's node order
      node_features = np.column_stack([np.asarray(v) for v in morph.values()])
      ```
      
      ### Cell Type Annotations as Node Features
      
      Append cell-type labels (e.g. from HoVer-Net) as an extra node-feature column or one-hot block before building the graph:
      
      ```python
      cell_types = hovernet_type_predictions  # array of per-cell type ids, aligned to props order
      onehot = np.eye(n_classes)[cell_types]
      node_features = np.column_stack([node_features, onehot])
      ```
      
      ## Spatial Analysis
      
      PathML builds the graph; for downstream spatial statistics (neighborhood enrichment, co-occurrence, interaction tests, spatial autocorrelation) move the per-cell coordinates + labels into AnnData and use **squidpy** — the dedicated, maintained tool for this. (`pathml.graph` does not provide `analyze_neighborhoods`, `spatial_clustering`, `cell_interaction_analysis`, or `spatial_statistics` functions; don't assume they exist.) See `multiparametric.md` for the AnnData-based squidpy workflow.
      
      ```python
      import anndata as ad
      import numpy as np
      import squidpy as sq
      
      # Wrap centroids + cell types in AnnData
      adata = ad.AnnData(node_features)
      adata.obsm["spatial"] = centroids
      adata.obs["cell_type"] = cell_type_labels.astype(str)
      
      # Spatial neighbors + neighborhood enrichment
      sq.gr.spatial_neighbors(adata, coord_type="generic")
      sq.gr.nhood_enrichment(adata, cluster_key="cell_type")
      sq.pl.nhood_enrichment(adata, cluster_key="cell_type")
      ```
      
      For plain spatial clustering of positions, scikit-learn's `DBSCAN`/`KMeans` on `centroids` works directly.
      
      ## Integration with Graph Neural Networks
      
      ### Convert to PyTorch Geometric Format
      
      The `Graph` returned by a builder is already a `torch_geometric.data.Data`-style object, so it plugs straight into PyTorch Geometric (no conversion helper needed):
      
      ```python
      import torch
      from torch_geometric.nn import GCNConv
      from pathml.graph import KNNGraphBuilder
      
      graph = KNNGraphBuilder(k=5, thresh=50).process(inst_map, features=features, centroids=centroids)
      
      x = graph.node_features        # node features
      edge_index = graph.edge_index  # (2, n_edges)
      
      class GNN(torch.nn.Module):
          def __init__(self, in_channels, hidden_channels, out_channels):
              super().__init__()
              self.conv1 = GCNConv(in_channels, hidden_channels)
              self.conv2 = GCNConv(hidden_channels, out_channels)
      
          def forward(self, data):
              x = self.conv1(data.node_features, data.edge_index).relu()
              return self.conv2(x, data.edge_index)
      
      model = GNN(in_channels=x.shape[1], hidden_channels=64, out_channels=5)
      output = model(graph)
      ```
      
      ### Dataset of Graphs for Multiple Slides
      
      Use PathML's `EntityDataset` (from `pathml.datasets`) to serve multiple graphs, or collect them into a list and batch with PyG's `DataLoader`:
      
      ```python
      from torch_geometric.loader import DataLoader
      from pathml.graph import KNNGraphBuilder
      
      builder = KNNGraphBuilder(k=5, thresh=50)
      graphs = [
          builder.process(s_inst_map, features=s_features, centroids=s_centroids)
          for (s_inst_map, s_features, s_centroids) in per_slide_inputs
      ]
      
      loader = DataLoader(graphs, batch_size=32, shuffle=True)
      for batch in loader:
          output = model(batch)
          loss = criterion(output, batch.y)
          loss.backward()
          optimizer.step()
      ```
      
      ## Visualization
      
      ### Graph Visualization
      
      Draw the graph directly from its `edge_index` and the cell `centroids` (no NetworkX conversion required):
      
      ```python
      import matplotlib.pyplot as plt
      
      edges = graph.edge_index.cpu().numpy()  # (2, n_edges)
      
      fig, ax = plt.subplots(figsize=(12, 12))
      # Edges
      for s, d in edges.T:
          p1, p2 = centroids[s], centroids[d]
          ax.plot([p1[0], p2[0]], [p1[1], p2[1]], "b-", alpha=0.3, linewidth=0.5)
      # Nodes colored by type
      ax.scatter(centroids[:, 0], centroids[:, 1], c=cell_type_labels, cmap="tab10", s=20)
      ax.set_aspect("equal")
      ax.axis("off")
      plt.title("Cell Graph")
      plt.show()
      ```
      
      ### Overlay on Tissue Image
      
      Same as above but `imshow` the tissue image first, then draw edges/nodes on the same axes using `centroids` in image (x, y) coordinates.
      
      ## Complete Workflow Example
      
      ```python
      import numpy as np
      from skimage.measure import regionprops, regionprops_table
      from pathml.core import CODEXSlide
      from pathml.preprocessing import Pipeline, CollapseRunsCODEX, SegmentMIF
      from pathml.graph import KNNGraphBuilder
      
      # 1. Load and preprocess slide
      slide = CODEXSlide("path/to/codex", stain="IF")
      pipeline = Pipeline([
          CollapseRunsCODEX(z=0),
          SegmentMIF(model="mesmer", nuclear_channel=0, cytoplasm_channel=29),
      ])
      slide.run(pipeline)
      
      # 2. Per-cell centroids + features from the instance mask
      inst_map = slide.masks["cell_segmentation"]
      props = regionprops(inst_map)
      centroids = np.array([p.centroid[::-1] for p in props])  # (x, y)
      morph = regionprops_table(
          inst_map, properties=["area", "perimeter", "eccentricity", "solidity"]
      )
      features = np.column_stack([np.asarray(v) for v in morph.values()])
      
      # 3. Build the cell graph
      graph = KNNGraphBuilder(k=6, thresh=50).process(
          inst_map, features=features, centroids=centroids
      )
      
      # 4. graph is PyG-ready (graph.node_features, graph.edge_index) -> feed to a GNN
      #    For neighborhood/interaction statistics, hand centroids + labels to squidpy
      #    (see "Spatial Analysis" above).
      ```
      
      ## Performance Considerations
      
      **Large tissue sections:**
      - Build graphs tile-by-tile, then merge
      - Use sparse adjacency matrices
      - Leverage GPU for feature extraction
      
      **Memory efficiency:**
      - Store only necessary edge features
      - Use int32/float32 instead of int64/float64
      - Batch process multiple slides
      
      **Computational efficiency:**
      - Parallelize feature extraction across cells
      - Use KNN for faster neighbor queries
      - Cache computed features
      
      ## Best Practices
      
      1. **Choose an appropriate builder:** `KNNGraphBuilder` for cell graphs (tune `k`/`thresh`), `RAGGraphBuilder` for superpixel/tissue adjacency, `MSTGraphBuilder` for a sparse backbone
      
      2. **Normalize features:** Scale morphological and intensity features for GNN compatibility
      
      3. **Handle edge effects:** Exclude boundary cells or use tissue masks to define valid regions
      
      4. **Validate graph construction:** Visualize graphs on small regions before large-scale processing
      
      5. **Combine multiple feature types:** Morphology + intensity + texture provides rich representations
      
      6. **Consider tissue context:** Tissue type affects appropriate graph parameters (connectivity, radius)
      
      ## Common Issues and Solutions
      
      **Issue: Too many/few edges**
      - Adjust `k` and the `thresh` distance cutoff on `KNNGraphBuilder`
      - Verify pixel-to-micron conversion for biological relevance
      
      **Issue: Memory errors with large graphs**
      - Process tiles separately and merge graphs
      - Use sparse representations and float32 features
      
      **Issue: Missing cells at tissue boundaries**
      - Use tissue masks to exclude invalid regions before building the graph
      
      **Issue: Inconsistent feature scales**
      - Normalize features: `(x - mean) / std`
      - Use robust scaling for outliers
      
      ## Additional Resources
      
      - **PathML Graph API:** https://pathml.readthedocs.io/en/latest/api_graph_reference.html
      - **PyTorch Geometric:** https://pytorch-geometric.readthedocs.io/
      - **NetworkX:** https://networkx.org/
      - **Spatial Statistics:** Baddeley et al., "Spatial Point Patterns: Methodology and Applications with R"
      
    • image_loading.md 13.2 KB
      # Image Loading & Formats
      
      ## Overview
      
      PathML provides comprehensive support for loading whole-slide images (WSI) from 160+ proprietary medical imaging formats. The framework abstracts vendor-specific complexities through unified slide classes and interfaces, enabling seamless access to image pyramids, metadata, and regions of interest across different file formats.
      
      ## Supported Formats
      
      PathML supports the following slide formats:
      
      ### Brightfield Microscopy Formats
      - **Aperio SVS** (`.svs`) - Leica Biosystems
      - **Hamamatsu NDPI** (`.ndpi`) - Hamamatsu Photonics
      - **Leica SCN** (`.scn`) - Leica Biosystems
      - **Zeiss ZVI** (`.zvi`) - Carl Zeiss
      - **3DHISTECH** (`.mrxs`) - 3DHISTECH Ltd.
      - **Ventana BIF** (`.bif`) - Roche Ventana
      - **Generic tiled TIFF** (`.tif`, `.tiff`)
      
      ### Medical Imaging Standards
      - **DICOM** (`.dcm`) - Digital Imaging and Communications in Medicine
      - **OME-TIFF** (`.ome.tif`, `.ome.tiff`) - Open Microscopy Environment
      
      ### Multiparametric Imaging
      - **CODEX** - Spatial proteomics imaging
      - **Vectra** (`.qptiff`) - Multiplex immunofluorescence
      - **MERFISH** - Multiplexed error-robust FISH
      
      PathML leverages OpenSlide and other specialized libraries to handle format-specific nuances automatically.
      
      ## Core Classes for Loading Images
      
      ### SlideData
      
      `SlideData` is the fundamental class for representing whole-slide images in PathML. You construct it directly (there is no `SlideData.from_slide` factory) or via a convenience subclass (`HESlide`, `VectraSlide`, `CODEXSlide`, `MultiparametricSlide`, `IHCSlide`).
      
      **Loading from file:**
      ```python
      from pathml.core import SlideData, HESlide, types
      
      # Convenience subclass for H&E (recommended for brightfield H&E)
      wsi = HESlide("path/to/slide.svs", name="example")
      
      # Generic constructor. backend is a STRING ("openslide" | "bioformats" | "dicom");
      # slide_type carries the stain/dimensionality (use a pathml.core.types instance).
      wsi = SlideData("path/to/slide.svs", backend="openslide", slide_type=types.HE)
      
      # OME-TIFF via the Bio-Formats backend
      wsi = SlideData("path/to/slide.ome.tiff", backend="bioformats")
      ```
      
      **Key attributes:**
      - `wsi.slide` - Backend slide object (OpenSlide, BioFormats, etc.)
      - `wsi.tiles` - Collection of image tiles (populated after a tiling/Pipeline run)
      - `wsi.masks` - Slide-level masks
      - `wsi.shape` - Image dimensions
      - `wsi.name` - Slide name
      
      **Methods:**
      - `wsi.run(pipeline, ...)` - Run a preprocessing Pipeline (handles tiling + transforms)
      - `wsi.generate_tiles()` - Generator over `Tile` objects
      - `wsi.extract_region(location, size, ...)` - Read a specific region (delegates to the backend)
      - `wsi.write(path)` - Write contents to disk in h5path format
      
      ### SlideType and types
      
      `SlideType` describes a slide's stain/dimensionality (NOT the backend). The `pathml.core.types` module exposes pre-made instances you pass as `slide_type`:
      
      ```python
      from pathml.core import types
      
      types.HE       # H&E brightfield
      types.IHC      # immunohistochemistry
      types.IF       # immunofluorescence
      types.CODEX    # CODEX multiplex IF
      types.Vectra   # Vectra multiplex IF
      ```
      
      The decoding backend is selected separately with the string `backend` argument (`"openslide"`, `"bioformats"`, or `"dicom"`).
      
      ### Specialized Slide Classes
      
      PathML provides specialized slide classes for specific imaging modalities:
      
      **CODEXSlide:**
      ```python
      from pathml.core import CODEXSlide
      
      # Load CODEX spatial proteomics data
      codex_slide = CODEXSlide(
          path="path/to/codex_dir",
          stain="IF",  # Immunofluorescence
          backend="bioformats"
      )
      ```
      
      **VectraSlide:**
      ```python
      from pathml.core import VectraSlide
      
      # Load Vectra multiplex IF data (.qptiff); uses the Bio-Formats backend
      vectra_slide = VectraSlide("path/to/vectra.qptiff")
      ```
      
      **MultiparametricSlide:**
      ```python
      from pathml.core import MultiparametricSlide
      
      # Generic multiparametric imaging
      mp_slide = MultiparametricSlide("path/to/multiparametric_data", backend="bioformats")
      ```
      
      ## Loading Strategies
      
      ### Tile-Based Loading
      
      For large WSI files, tile-based loading enables memory-efficient processing. `generate_tiles` returns a generator over `Tile` objects; iterate it directly. To persist tiles on the slide, run a `Pipeline` (see `preprocessing.md`), after which they are available on `wsi.tiles`.
      
      ```python
      from pathml.core import HESlide
      
      # Load slide
      wsi = HESlide("path/to/slide.svs")
      
      # Iterate tiles lazily at a chosen level
      for tile in wsi.generate_tiles(
          level=0,        # Pyramid level (0 = highest resolution)
          shape=256,      # Tile dimensions in pixels
          stride=256,     # Spacing between tiles (256 = no overlap)
          pad=False,      # Whether to pad edge tiles
      ):
          image = tile.image   # numpy array
          coords = tile.coords  # (i, j) coordinates
          # Process tile...
      ```
      
      **Overlapping tiles:**
      ```python
      # 50% overlap
      for tile in wsi.generate_tiles(level=0, shape=256, stride=128):
          ...
      ```
      
      Confirm the exact tiling keyword (`shape` vs `tile_size`) against your installed PathML version; it has differed across releases.
      
      ### Region-Based Loading
      
      Extract specific regions of interest directly:
      
      ```python
      # Read region at a specific location. extract_region delegates to the
      # active backend (OpenSlide/Bio-Formats/DICOM); see your backend for the
      # exact level/coordinate semantics.
      region = wsi.extract_region(
          location=(10000, 15000),  # (x, y) coordinates
          size=(512, 512),          # width, height in pixels
      )
      
      # Returns a numpy array
      ```
      
      ### Pyramid Level Selection
      
      Whole-slide images are stored in multi-resolution pyramids. Select the appropriate level based on desired magnification. Pyramid metadata lives on the backend object (`wsi.slide`); for OpenSlide it exposes `level_dimensions` and `level_downsamples`:
      
      ```python
      # Inspect available levels via the OpenSlide backend
      osh = wsi.slide.slide  # underlying openslide.OpenSlide handle
      print(osh.level_dimensions)   # [(width0, height0), (width1, height1), ...]
      print(osh.level_downsamples)  # [1.0, 4.0, 16.0, ...]
      
      # Tile at a lower resolution for faster processing
      for tile in wsi.generate_tiles(level=2, shape=256):  # level 2 (e.g. 16x downsampled)
          ...
      ```
      
      **Common pyramid levels:**
      - Level 0: Full resolution (e.g., 40x magnification)
      - Level 1: 4x downsampled (e.g., 10x magnification)
      - Level 2: 16x downsampled (e.g., 2.5x magnification)
      - Level 3: 64x downsampled (thumbnail)
      
      ### Thumbnail Loading
      
      Generate low-resolution thumbnails for visualization and quality control. PathML exposes `wsi.plot()` for a quick thumbnail view; for an array, pull one from the backend handle:
      
      ```python
      import matplotlib.pyplot as plt
      
      # Quick built-in thumbnail plot
      wsi.plot()
      
      # Or get an array via the OpenSlide backend handle
      thumbnail = wsi.slide.slide.get_thumbnail((1024, 1024))
      plt.imshow(thumbnail)
      plt.axis("off")
      plt.show()
      ```
      
      ## Batch Loading with SlideDataset
      
      Process multiple slides efficiently using `SlideDataset`, which wraps a list of `SlideData` objects (not raw paths):
      
      ```python
      from pathml.core import HESlide, SlideDataset
      import glob
      
      # Build slide objects, then a dataset
      slide_paths = glob.glob("data/*.svs")
      slides = [HESlide(p) for p in slide_paths]
      dataset = SlideDataset(slides)
      
      # Access individual slides
      for slide in dataset.slides:
          print(slide.name)
      ```
      
      **With preprocessing pipeline:**
      ```python
      from pathml.preprocessing import Pipeline, StainNormalizationHE
      
      pipeline = Pipeline([
          StainNormalizationHE(target="normalize"),
      ])
      
      # Run across the dataset. PathML parallelizes with Dask via a client;
      # pass a dask.distributed Client (see the distributed example below).
      dataset.run(pipeline)
      ```
      
      ## Metadata Access
      
      Extract slide metadata including acquisition parameters, magnification, and vendor-specific information. For OpenSlide-backed slides the vendor properties live on the backend handle:
      
      ```python
      # OpenSlide properties dictionary
      props = wsi.slide.slide.properties
      
      print(props.get("openslide.objective-power"))  # Magnification
      print(props.get("openslide.mpp-x"))            # Microns per pixel X
      print(props.get("openslide.mpp-y"))            # Microns per pixel Y
      print(props.get("openslide.vendor"))           # Scanner vendor
      
      # Slide dimensions
      print(wsi.shape)  # full-resolution dimensions
      ```
      
      ## Working with DICOM Slides
      
      PathML supports DICOM WSI through specialized handling:
      
      ```python
      from pathml.core import SlideData
      
      # Load DICOM WSI (backend is the string "dicom")
      dicom_slide = SlideData("path/to/slide.dcm", backend="dicom")
      ```
      
      ## Working with OME-TIFF
      
      OME-TIFF provides an open standard for multi-dimensional imaging:
      
      ```python
      from pathml.core import SlideData
      
      # Load OME-TIFF via the Bio-Formats backend (requires a JDK / JVM)
      ome_slide = SlideData("path/to/slide.ome.tiff", backend="bioformats")
      
      # Inspect dimensions / channels
      print(ome_slide.shape)
      ```
      
      ## Performance Considerations
      
      ### Memory Management
      
      For large WSI files (often >1GB), use tile-based loading to avoid memory exhaustion:
      
      ```python
      # Efficient: stream tiles one at a time
      for tile in wsi.generate_tiles(level=1, shape=256):
          process_tile(tile)
      
      # Inefficient: pulling the whole level-0 image into memory may crash
      full_image = wsi.extract_region(location=(0, 0), size=wsi.shape)
      ```
      
      ### Distributed Processing
      
      Use Dask for parallel processing across multiple workers:
      
      ```python
      from pathml.core import HESlide, SlideDataset
      from dask.distributed import Client
      
      # Start a Dask client and pass it to run()
      client = Client(n_workers=8, threads_per_worker=2)
      
      dataset = SlideDataset([HESlide(p) for p in slide_paths])
      dataset.run(pipeline, client=client)
      ```
      
      ### Level Selection
      
      Balance resolution and performance by selecting appropriate pyramid levels:
      
      - **Level 0:** Use for final analysis requiring maximum detail
      - **Level 1-2:** Use for most preprocessing and model training
      - **Level 3+:** Use for thumbnails, quality control, and rapid exploration
      
      ## Common Issues and Solutions
      
      **Issue: Slide fails to load**
      - Verify file format is supported
      - Check file permissions and path
      - Try different backend: `backend="bioformats"` or `backend="openslide"`
      
      **Issue: Out of memory errors**
      - Use tile-based loading instead of full-slide loading
      - Process at lower pyramid level (e.g., level=1 or level=2)
      - Reduce tile_size parameter
      - Enable distributed processing with Dask
      
      **Issue: Color inconsistencies across slides**
      - Apply stain normalization preprocessing (see `preprocessing.md`)
      - Check scanner metadata for calibration information
      - Use `StainNormalizationHE` transform in preprocessing pipeline
      
      **Issue: Metadata missing or incorrect**
      - Different vendors store metadata in different locations
      - Use `wsi.metadata` to inspect available fields
      - Some formats may have limited metadata support
      
      ## Best Practices
      
      1. **Always inspect pyramid structure** before processing: Check `level_dimensions` and `level_downsamples` to understand available resolutions
      
      2. **Use appropriate pyramid levels**: Process at level 1-2 for most tasks; reserve level 0 for final high-resolution analysis
      
      3. **Tile with overlap** for segmentation tasks: Use stride < tile_size to avoid edge artifacts
      
      4. **Verify magnification consistency**: Check `openslide.objective-power` metadata when combining slides from different sources
      
      5. **Handle vendor-specific formats**: Use specialized slide classes (CODEXSlide, VectraSlide) for multiparametric data
      
      6. **Implement quality control**: Generate thumbnails and inspect for artifacts before processing
      
      7. **Use distributed processing** for large datasets: Leverage Dask for parallel processing across multiple workers
      
      ## Example Workflows
      
      ### Loading and Inspecting a New Slide
      
      ```python
      from pathml.core import HESlide
      
      # Load slide
      wsi = HESlide("path/to/slide.svs", name="example")
      
      # Inspect properties
      print(f"Shape: {wsi.shape}")
      props = wsi.slide.slide.properties  # OpenSlide properties
      print(f"Magnification: {props.get('openslide.objective-power')}")
      
      # Quick thumbnail for QC
      wsi.plot()
      ```
      
      ### Processing Multiple Slides
      
      ```python
      from pathml.core import HESlide, SlideDataset
      from pathml.preprocessing import Pipeline, TissueDetectionHE
      from dask.distributed import Client
      import glob
      
      # Find all slides
      slide_paths = glob.glob("data/slides/*.svs")
      
      # Create pipeline
      pipeline = Pipeline([TissueDetectionHE()])
      
      # Build dataset from slide objects and run with a Dask client
      dataset = SlideDataset([HESlide(p) for p in slide_paths])
      client = Client(n_workers=8, threads_per_worker=2)
      dataset.run(pipeline, client=client)
      
      # Persist each processed slide to h5path
      dataset.write("processed/")
      ```
      
      ### Loading CODEX Multiparametric Data
      
      ```python
      from pathml.core import CODEXSlide
      from pathml.preprocessing import Pipeline, CollapseRunsCODEX, SegmentMIF
      
      # Load CODEX slide
      codex = CODEXSlide("path/to/codex_dir", stain="IF")
      
      # Create CODEX-specific pipeline. SegmentMIF channels are integer indices
      # into the collapsed channel stack (see multiparametric.md).
      pipeline = Pipeline([
          CollapseRunsCODEX(z=0),  # select z-plane
          SegmentMIF(
              model="mesmer",
              nuclear_channel=0,
              cytoplasm_channel=29,
          ),
      ])
      
      codex.run(pipeline)
      ```
      
      ## Additional Resources
      
      - **PathML Documentation:** https://pathml.readthedocs.io/
      - **OpenSlide:** https://openslide.org/ (underlying library for WSI formats)
      - **Bio-Formats:** https://www.openmicroscopy.org/bio-formats/ (alternative backend)
      - **DICOM Standard:** https://www.dicomstandard.org/
      
    • machine_learning.md 15.6 KB
      # Machine Learning
      
      ## Overview
      
      PathML provides comprehensive machine learning capabilities for computational pathology, including pre-built models for nucleus detection and segmentation, PyTorch-integrated training workflows, public dataset access, and ONNX-based inference deployment. The framework seamlessly bridges image preprocessing with deep learning to enable end-to-end pathology ML pipelines.
      
      ## Pre-Built Models
      
      PathML includes state-of-the-art pre-trained models for nucleus analysis:
      
      ### HoVer-Net
      
      **HoVer-Net** (Horizontal and Vertical Network) performs simultaneous nucleus instance segmentation and classification.
      
      **Architecture:**
      - Encoder-decoder structure with three prediction branches:
        - **Nuclear Pixel (NP)** - Binary segmentation of nuclear regions
        - **Horizontal-Vertical (HV)** - Distance maps to nucleus centroids
        - **Classification (NC)** - Nucleus type classification
      
      **Nucleus types:**
      1. Epithelial
      2. Inflammatory
      3. Connective/Soft tissue
      4. Dead/Necrotic
      5. Background
      
      **Usage:**
      ```python
      from pathml.ml import HoVerNet
      import torch
      
      # Construct the model (n_classes = number of nucleus types).
      model = HoVerNet(n_classes=5)
      
      # Move to GPU if available
      device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
      model = model.to(device)
      
      # Inference on a tile. HoVerNet returns a list/tuple of branch outputs
      # (NP, HV, and the classification branch when n_classes is set).
      tile_image = torch.from_numpy(tile.image).permute(2, 0, 1).unsqueeze(0).float()
      tile_image = tile_image.to(device)
      
      with torch.no_grad():
          outputs = model(tile_image)
      ```
      
      For pretrained weights, download a published HoVer-Net checkpoint and load it with `model.load_state_dict(...)`; the constructor does not fetch weights itself. Confirm the exact constructor argument (`n_classes`) and the branch output layout against the API docs for your installed version.
      
      **Post-processing** (the real function is `post_process_batch_hovernet`, not `hovernet_postprocess`):
      ```python
      from pathml.ml import post_process_batch_hovernet
      
      # Convert a batch of model outputs to instance + type maps
      instance_map, type_map = post_process_batch_hovernet(outputs, n_classes=5)
      
      # instance_map: each nucleus has a unique ID
      # type_map: each nucleus assigned a type
      ```
      
      ### HACTNet
      
      **HACTNet** is a graph-neural-network model that operates on hierarchical cell-graph + tissue-graph (HACT) representations of a tissue region for region/graph-level classification. It is not a per-pixel nucleus classifier; pair it with the graph builders in `pathml.graph` (see `graphs.md`).
      
      ```python
      from pathml.ml import HACTNet
      
      # HACTNet consumes batched cell-graph and tissue-graph inputs constructed
      # from segmented tissue. Check the API docs for the exact constructor
      # arguments and the expected graph batch format for your version.
      model = HACTNet(...)
      ```
      
      ## Training Workflows
      
      ### Dataset Preparation
      
      PathML provides PyTorch-compatible dataset classes in `pathml.datasets` (note: NOT `pathml.ml`):
      
      **TileDataset** wraps an on-disk h5path slide so its tiles can be served to a PyTorch `DataLoader`:
      ```python
      from pathml.datasets import TileDataset
      from torch.utils.data import DataLoader
      
      # Point at an h5path file written by SlideData.write(...)
      tile_dataset = TileDataset("processed/slide001.h5path")
      
      loader = DataLoader(tile_dataset, batch_size=32, shuffle=True, num_workers=4)
      for images, masks, labels in loader:
          ...
      ```
      
      `pathml.datasets` also provides `EntityDataset` (for graph/entity inputs) and ready-made downloadable DataModules, `PanNukeDataModule` and `DeepFocusDataModule`. There is no `PathMLDataModule`; build train/val/test `DataLoader`s yourself or use one of the provided DataModules. Confirm the `TileDataset.__getitem__` return tuple against your version.
      
      ### Training HoVer-Net
      
      Complete workflow for training HoVer-Net on custom data:
      
      PathML ships the composite HoVer-Net loss as `loss_hovernet` (combining the NP, HV, and classification branch terms) — use it rather than reimplementing the loss by hand.
      
      ```python
      import torch
      from torch.utils.data import DataLoader
      from pathml.ml import HoVerNet, loss_hovernet, post_process_batch_hovernet
      from pathml.datasets import PanNukeDataModule
      
      # 1. Prepare data (PanNukeDataModule lives in pathml.datasets)
      data_module = PanNukeDataModule(
          data_dir="path/to/pannuke",
          batch_size=8,
          nucleus_type_labels=True,
      )
      train_loader = data_module.train_dataloader
      
      # 2. Initialize model
      model = HoVerNet(n_classes=5)
      
      # 3. Use PathML's HoVer-Net loss
      def criterion(outputs, ground_truth, n_classes=5):
          return loss_hovernet(outputs, ground_truth, n_classes=n_classes)
      
      # 4. Configure optimizer
      optimizer = torch.optim.Adam(model.parameters(), lr=1e-4, weight_decay=1e-5)
      
      scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(
          optimizer,
          mode='min',
          factor=0.5,
          patience=10
      )
      
      # 5. Training loop.
      # PanNukeDataModule yields (images, masks, tissue_types) batches; the
      # ground-truth tensors required by loss_hovernet (NP / HV / NC targets)
      # are derived from the masks. Consult the PanNuke example in the PathML
      # docs for the exact unpacking for your version.
      device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
      model = model.to(device)
      
      num_epochs = 100
      for epoch in range(num_epochs):
          model.train()
          train_loss = 0.0
          n_batches = 0
      
          for images, masks, tissue_type in train_loader:
              images = images.float().to(device)
              ground_truth = masks.to(device)  # see docs for target construction
      
              optimizer.zero_grad()
              outputs = model(images)
              loss = loss_hovernet(outputs, ground_truth, n_classes=5)
              loss.backward()
              optimizer.step()
      
              train_loss += loss.item()
              n_batches += 1
      
          scheduler.step(train_loss)
          print(f"Epoch {epoch+1}/{num_epochs}  Train Loss: {train_loss / max(n_batches, 1):.4f}")
      
          if (epoch + 1) % 10 == 0:
              torch.save(model.state_dict(), f"hovernet_epoch_{epoch+1}.pth")
      ```
      
      ## Public Datasets
      
      PathML bundles two downloadable DataModules in `pathml.datasets`: `PanNukeDataModule` and `DeepFocusDataModule`. (There is no built-in TCGA DataModule — download TCGA WSIs via the GDC portal and wrap them with `SlideDataset` / `TileDataset`.)
      
      ### PanNuke Dataset
      
      **PanNuke** is a multi-tissue histology dataset with nucleus instance + type annotations across several cell categories, commonly used to train HoVer-Net.
      
      ```python
      from pathml.datasets import PanNukeDataModule
      
      # PanNukeDataModule will download the data to data_dir on first use.
      pannuke = PanNukeDataModule(
          data_dir="path/to/pannuke",
          batch_size=16,
          nucleus_type_labels=True,  # include per-nucleus type labels
      )
      
      # DataLoaders are exposed as properties (not methods)
      train_loader = pannuke.train_dataloader
      valid_loader = pannuke.valid_dataloader
      test_loader = pannuke.test_dataloader
      
      # Each batch is a tuple: (images, masks, tissue_type)
      for images, masks, tissue_type in train_loader:
          ...
      ```
      
      Confirm the exact constructor arguments and batch tuple layout against the API docs for your installed version.
      
      ### Custom Dataset Integration
      
      Create custom datasets for PathML workflows:
      
      ```python
      from torch.utils.data import Dataset
      import numpy as np
      from pathlib import Path
      
      class CustomPathologyDataset(Dataset):
          def __init__(self, data_dir, transform=None):
              self.data_dir = Path(data_dir)
              self.image_paths = list(self.data_dir.glob('images/*.png'))
              self.transform = transform
      
          def __len__(self):
              return len(self.image_paths)
      
          def __getitem__(self, idx):
              # Load image
              image_path = self.image_paths[idx]
              image = np.array(Image.open(image_path))
      
              # Load corresponding annotation
              annot_path = self.data_dir / 'annotations' / f'{image_path.stem}.npy'
              annotation = np.load(annot_path)
      
              # Apply transforms
              if self.transform:
                  image = self.transform(image)
      
              return {
                  'image': torch.from_numpy(image).permute(2, 0, 1).float(),
                  'annotation': torch.from_numpy(annotation).long(),
                  'path': str(image_path)
              }
      
      # Use in PathML workflow
      dataset = CustomPathologyDataset('path/to/data')
      dataloader = DataLoader(dataset, batch_size=16, shuffle=True, num_workers=4)
      ```
      
      ## Data Augmentation
      
      Apply augmentations to improve model generalization:
      
      ```python
      import albumentations as A
      from albumentations.pytorch import ToTensorV2
      
      # Define augmentation pipeline
      train_transform = A.Compose([
          A.RandomRotate90(p=0.5),
          A.Flip(p=0.5),
          A.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2, hue=0.1, p=0.5),
          A.GaussianBlur(blur_limit=(3, 7), p=0.3),
          A.ElasticTransform(alpha=1, sigma=50, alpha_affine=50, p=0.3),
          A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
          ToTensorV2()
      ])
      
      val_transform = A.Compose([
          A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
          ToTensorV2()
      ])
      
      # Apply augmentations inside your Dataset's __getitem__ (e.g. the
      # CustomPathologyDataset above), or wrap a TileDataset and transform each
      # tile before returning it. Pass the transform to whichever Dataset you use.
      ```
      
      ## Model Evaluation
      
      ### Metrics
      
      Standard nucleus-segmentation metrics — Dice, Aggregated Jaccard Index (AJI), and Panoptic Quality (PQ = SQ x RQ) — are not bundled as a `pathml.ml.metrics` module. Use a published implementation (e.g. the HoVer-Net authors' `compute_stats` code, or a maintained instance-segmentation metrics package) and feed it the post-processed instance + type maps.
      
      ### Evaluation Loop
      
      ```python
      import torch
      from pathml.ml import post_process_batch_hovernet
      
      model.eval()
      all_pred_inst, all_pred_type = [], []
      
      with torch.no_grad():
          for images, masks, tissue_type in test_loader:
              images = images.float().to(device)
              outputs = model(images)
      
              # Post-process the whole batch at once
              inst_map, type_map = post_process_batch_hovernet(outputs, n_classes=5)
              all_pred_inst.append(inst_map)
              all_pred_type.append(type_map)
      
      # Pass all_pred_inst/all_pred_type plus the ground-truth instance/type maps
      # to your chosen AJI / PQ implementation.
      ```
      
      ## ONNX Inference
      
      Deploy models using ONNX for production inference:
      
      ### Export to ONNX
      
      ```python
      import torch
      from pathml.ml import HoVerNet
      
      # Load trained model and weights
      model = HoVerNet(n_classes=5)
      model.load_state_dict(torch.load("hovernet_epoch_100.pth"))
      model.eval()
      
      # Create dummy input
      dummy_input = torch.randn(1, 3, 256, 256)
      
      # Export to ONNX
      torch.onnx.export(
          model,
          dummy_input,
          'hovernet_model.onnx',
          export_params=True,
          opset_version=11,
          input_names=['input'],
          output_names=['np_output', 'hv_output', 'nc_output'],
          dynamic_axes={
              'input': {0: 'batch_size'},
              'np_output': {0: 'batch_size'},
              'hv_output': {0: 'batch_size'},
              'nc_output': {0: 'batch_size'}
          }
      )
      ```
      
      ### ONNX Runtime Inference
      
      ```python
      import onnxruntime as ort
      import numpy as np
      
      # Load ONNX model
      session = ort.InferenceSession('hovernet_model.onnx')
      
      # Prepare input
      input_name = session.get_inputs()[0].name
      tile_image = preprocess_tile(tile)  # Normalize, transpose to (1, 3, H, W)
      
      # Run inference
      outputs = session.run(None, {input_name: tile_image})
      
      # Post-process with PathML's batch post-processor (rebuild the tensor
      # structure it expects from the ONNX outputs first)
      from pathml.ml import post_process_batch_hovernet
      inst_map, type_map = post_process_batch_hovernet(outputs, n_classes=5)
      ```
      
      ### Batch Inference Pipeline
      
      ```python
      from pathml.core import HESlide
      from pathml.ml import post_process_batch_hovernet
      import onnxruntime as ort
      
      def run_onnx_inference_pipeline(slide_path, onnx_model_path):
          wsi = HESlide(slide_path)
          session = ort.InferenceSession(onnx_model_path)
          input_name = session.get_inputs()[0].name
      
          results = []
          for tile in wsi.generate_tiles(level=1, shape=256, stride=256):
              tile_array = preprocess_tile(tile.image)  # normalize -> (1, 3, H, W)
              outputs = session.run(None, {input_name: tile_array})
              inst_map, type_map = post_process_batch_hovernet(outputs, n_classes=5)
              results.append({
                  "coords": tile.coords,
                  "instance_map": inst_map,
                  "type_map": type_map,
              })
          return results
      
      results = run_onnx_inference_pipeline("slide.svs", "hovernet_model.onnx")
      ```
      
      ## Transfer Learning
      
      Fine-tune pre-trained models on custom datasets:
      
      ```python
      from pathml.ml import HoVerNet
      
      # Load a model and a pretrained checkpoint
      model = HoVerNet(n_classes=5)
      model.load_state_dict(torch.load("pretrained_hovernet.pth"))
      
      # Freeze encoder layers for initial training
      for name, param in model.named_parameters():
          if 'encoder' in name:
              param.requires_grad = False
      
      # Fine-tune only decoder and classification heads
      optimizer = torch.optim.Adam(
          filter(lambda p: p.requires_grad, model.parameters()),
          lr=1e-4
      )
      
      # Train for a few epochs
      train_for_n_epochs(model, train_loader, optimizer, num_epochs=10)
      
      # Unfreeze all layers for full fine-tuning
      for param in model.parameters():
          param.requires_grad = True
      
      # Continue training with lower learning rate
      optimizer = torch.optim.Adam(model.parameters(), lr=1e-5)
      train_for_n_epochs(model, train_loader, optimizer, num_epochs=50)
      ```
      
      ## Best Practices
      
      1. **Use pre-trained weights when available:**
         - Load a published checkpoint with `model.load_state_dict(...)` for better initialization
         - Fine-tune on domain-specific data
      
      2. **Apply appropriate data augmentation:**
         - Rotate, flip for orientation invariance
         - Color jitter to handle staining variations
         - Elastic deformation for biological variability
      
      3. **Monitor multiple metrics:**
         - Track detection, segmentation, and classification separately
         - Use domain-specific metrics (AJI, PQ) beyond standard accuracy
      
      4. **Handle class imbalance:**
         - Weighted loss functions for rare cell types
         - Oversampling minority classes
         - Focal loss for hard examples
      
      5. **Validate on diverse tissue types:**
         - Ensure generalization across different tissues
         - Test on held-out anatomical sites
      
      6. **Optimize for inference:**
         - Export to ONNX for faster deployment
         - Batch tiles for efficient GPU utilization
         - Use mixed precision (FP16) when possible
      
      7. **Save checkpoints regularly:**
         - Keep best model based on validation metrics
         - Save optimizer state for training resumption
      
      ## Common Issues and Solutions
      
      **Issue: Poor segmentation at nucleus boundaries**
      - Use HV maps (horizontal-vertical) to separate touching nuclei
      - Increase weight of HV loss term
      - Apply morphological post-processing
      
      **Issue: Misclassification of similar cell types**
      - Increase classification loss weight
      - Add hierarchical classification (HACTNet)
      - Augment training data for confused classes
      
      **Issue: Training unstable or not converging**
      - Reduce learning rate
      - Use gradient clipping: `torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)`
      - Check for data preprocessing issues
      
      **Issue: Out of memory during training**
      - Reduce batch size
      - Use gradient accumulation
      - Enable mixed precision training: `torch.cuda.amp`
      
      **Issue: Model overfits to training data**
      - Increase data augmentation
      - Add dropout layers
      - Reduce model capacity
      - Use early stopping based on validation loss
      
      ## Additional Resources
      
      - **PathML ML API:** https://pathml.readthedocs.io/en/latest/api_ml_reference.html
      - **HoVer-Net Paper:** Graham et al., "HoVer-Net: Simultaneous Segmentation and Classification of Nuclei in Multi-Tissue Histology Images," Medical Image Analysis, 2019
      - **PanNuke Dataset:** https://warwick.ac.uk/fac/cross_fac/tia/data/pannuke
      - **PyTorch Lightning:** https://www.pytorchlightning.ai/
      - **ONNX Runtime:** https://onnxruntime.ai/
      
    • multiparametric.md 16.3 KB
      # Multiparametric Imaging
      
      ## Overview
      
      PathML provides specialized support for multiparametric imaging technologies that simultaneously measure multiple markers at single-cell resolution. These techniques include CODEX, Vectra multiplex immunofluorescence, MERFISH, and other spatial proteomics and transcriptomics platforms. PathML handles the unique data structures, processing requirements, and quantification workflows specific to each technology.
      
      ## Supported Technologies
      
      ### CODEX (CO-Detection by indEXing)
      - Cyclic immunofluorescence imaging
      - 40+ protein markers simultaneously
      - Single-cell spatial proteomics
      - Multi-cycle acquisition with antibody barcoding
      
      ### Vectra Polaris
      - Multispectral multiplex immunofluorescence
      - 6-8 markers per slide
      - Spectral unmixing
      - Whole-slide scanning
      
      ### MERFISH (Multiplexed Error-Robust FISH)
      - Spatial transcriptomics
      - 100s-1000s of genes
      - Single-molecule resolution
      - Error-correcting barcodes
      
      ### Other Platforms
      - CycIF (Cyclic Immunofluorescence)
      - IMC (Imaging Mass Cytometry)
      - MIBI (Multiplexed Ion Beam Imaging)
      
      ## CODEX Workflows
      
      ### Loading CODEX Data
      
      CODEX data is typically organized in multi-channel image stacks from multiple acquisition cycles:
      
      ```python
      from pathml.core import CODEXSlide
      
      # Load CODEX dataset (uses the Bio-Formats backend internally)
      codex_slide = CODEXSlide("path/to/codex_directory", stain="IF")
      
      # Inspect dimensions
      print(f"Image shape: {codex_slide.shape}")
      ```
      
      **CODEX directory structure:**
      ```
      codex_directory/
      ├── cyc001_reg001/
      │   ├── 1_00001_Z001_CH1.tif
      │   ├── 1_00001_Z001_CH2.tif
      │   └── ...
      ├── cyc002_reg001/
      │   └── ...
      └── channelnames.txt
      ```
      
      ### CODEX Preprocessing Pipeline
      
      Complete pipeline for CODEX data processing:
      
      ```python
      from pathml.preprocessing import Pipeline, CollapseRunsCODEX, SegmentMIF, QuantifyMIF
      
      # Create CODEX-specific pipeline
      codex_pipeline = Pipeline([
          # 1. Collapse the z-stack to a single multi-channel image
          CollapseRunsCODEX(z=0),
      
          # 2. Cell segmentation using Mesmer (integer channel indices)
          SegmentMIF(
              model="mesmer",
              nuclear_channel=0,        # e.g. DAPI index
              cytoplasm_channel=29,     # e.g. membrane/cytoplasm marker index
              image_resolution=0.377,   # microns per pixel
          ),
      
          # 3. Quantify per-cell marker expression across all channels
          QuantifyMIF(segmentation_mask="cell_segmentation"),
      ])
      
      # Run pipeline on the slide
      codex_slide.run(codex_pipeline)
      
      # Access results
      segmentation_mask = codex_slide.masks["cell_segmentation"]
      cell_data = codex_slide.counts  # AnnData object (cells x markers)
      ```
      
      ### CollapseRunsCODEX
      
      Consolidates multi-cycle CODEX acquisitions into a single multi-channel image:
      
      ```python
      from pathml.preprocessing import CollapseRunsCODEX
      
      transform = CollapseRunsCODEX(z=0)  # select the focal plane (0-indexed)
      ```
      
      **Parameters:**
      - `z`: which z-plane to extract from the CODEX z-stack (0-indexed)
      
      **Output:** Single multi-channel image with all markers stacked along the channel axis.
      
      ### Cell Segmentation with Mesmer
      
      DeepCell Mesmer provides accurate cell segmentation for multiparametric imaging:
      
      ```python
      from pathml.preprocessing import SegmentMIF
      
      transform = SegmentMIF(
          model="mesmer",            # DeepCell Mesmer model
          nuclear_channel=0,         # integer index of the nuclear marker (e.g. DAPI)
          cytoplasm_channel=29,      # integer index of a membrane/cytoplasm marker
          image_resolution=0.377,    # microns per pixel (important for accuracy)
      )
      ```
      
      `SegmentMIF` produces both `nuclear_segmentation` and `cell_segmentation` (whole-cell) masks. Tune behavior via `preprocess_kwargs`, `postprocess_kwargs_nuclear`, and `postprocess_kwargs_whole_cell`; there is no `compartment`/`min_cell_size`/`max_cell_size` argument.
      
      **Choosing the cytoplasm channel** (pick the index of a marker that outlines cells):
      - A pan-leukocyte marker (e.g. CD45) for immune-rich tissues
      - A pan-cytokeratin marker (e.g. panCK) for epithelial tissues
      - A universal membrane marker, or an averaged membrane channel
      
      ### Remote Segmentation
      
      Use the DeepCell service for segmentation without a local GPU:
      
      ```python
      from pathml.preprocessing import SegmentMIFRemote
      
      transform = SegmentMIFRemote(
          model="mesmer",
          nuclear_channel=0,
          cytoplasm_channel=29,
      )
      ```
      
      ### Marker Quantification
      
      Extract single-cell marker expression from segmented images:
      
      ```python
      from pathml.preprocessing import QuantifyMIF
      
      # Quantify per-cell expression across all channels of the segmented mask.
      transform = QuantifyMIF(segmentation_mask="cell_segmentation")
      ```
      
      **Output:** an AnnData object written to `slide.counts`:
      - `adata.X`: marker expression matrix (cells x channels)
      - `adata.obs`: per-cell metadata (coordinates, area, etc.)
      - `adata.var`: channel/marker metadata
      - `adata.obsm["spatial"]`: cell centroid coordinates
      
      ### Integration with AnnData
      
      Process multiple CODEX slides into unified AnnData object:
      
      ```python
      from pathml.core import CODEXSlide, SlideDataset
      from dask.distributed import Client
      import anndata as ad
      
      # Process multiple slides
      slide_paths = ["slide1", "slide2", "slide3"]
      dataset = SlideDataset([CODEXSlide(p, stain="IF") for p in slide_paths])
      
      client = Client(n_workers=8, threads_per_worker=2)
      dataset.run(codex_pipeline, client=client)
      
      # Concatenate the per-slide AnnData objects (each lives on slide.counts)
      combined_adata = ad.concat(
          [s.counts for s in dataset.slides],
          join="outer",
          label="Region",
          keys=slide_paths,
          index_unique="_",
      )
      
      # Save for downstream analysis
      combined_adata.write("codex_dataset.h5ad")
      ```
      
      ## Vectra Workflows
      
      ### Loading Vectra Data
      
      Vectra stores data in proprietary `.qptiff` format:
      
      ```python
      from pathml.core import VectraSlide
      
      # Load Vectra slide (.qptiff); VectraSlide uses the Bio-Formats backend
      vectra_slide = VectraSlide("path/to/slide.qptiff")
      
      print(f"Shape: {vectra_slide.shape}")
      ```
      
      ### Vectra Preprocessing
      
      ```python
      from pathml.preprocessing import Pipeline, CollapseRunsVectra, SegmentMIF, QuantifyMIF
      
      vectra_pipeline = Pipeline([
          # 1. Collapse the Vectra multi-channel data into a single image
          CollapseRunsVectra(),
      
          # 2. Cell segmentation (integer channel indices)
          SegmentMIF(
              model="mesmer",
              nuclear_channel=0,
              cytoplasm_channel=4,
              image_resolution=0.5,
          ),
      
          # 3. Quantification -> AnnData on slide.counts
          QuantifyMIF(segmentation_mask="cell_segmentation"),
      ])
      
      vectra_slide.run(vectra_pipeline)
      ```
      
      ## Downstream Analysis
      
      ### Cell Type Annotation
      
      Annotate cells based on marker expression:
      
      ```python
      import anndata as ad
      import numpy as np
      
      # Load quantified data
      adata = ad.read_h5ad('codex_dataset.h5ad')
      
      # Define cell types by marker thresholds
      def annotate_cell_types(adata, thresholds):
          cell_types = np.full(adata.n_obs, 'Unknown', dtype=object)
      
          # T cells: CD3+
          cd3_pos = adata[:, 'CD3'].X.flatten() > thresholds['CD3']
          cell_types[cd3_pos] = 'T cell'
      
          # CD4 T cells: CD3+ CD4+ CD8-
          cd4_tcells = (
              (adata[:, 'CD3'].X.flatten() > thresholds['CD3']) &
              (adata[:, 'CD4'].X.flatten() > thresholds['CD4']) &
              (adata[:, 'CD8'].X.flatten() < thresholds['CD8'])
          )
          cell_types[cd4_tcells] = 'CD4 T cell'
      
          # CD8 T cells: CD3+ CD8+ CD4-
          cd8_tcells = (
              (adata[:, 'CD3'].X.flatten() > thresholds['CD3']) &
              (adata[:, 'CD8'].X.flatten() > thresholds['CD8']) &
              (adata[:, 'CD4'].X.flatten() < thresholds['CD4'])
          )
          cell_types[cd8_tcells] = 'CD8 T cell'
      
          # B cells: CD20+
          b_cells = adata[:, 'CD20'].X.flatten() > thresholds['CD20']
          cell_types[b_cells] = 'B cell'
      
          # Macrophages: CD68+
          macrophages = adata[:, 'CD68'].X.flatten() > thresholds['CD68']
          cell_types[macrophages] = 'Macrophage'
      
          # Tumor cells: panCK+
          tumor = adata[:, 'panCK'].X.flatten() > thresholds['panCK']
          cell_types[tumor] = 'Tumor'
      
          return cell_types
      
      # Apply annotation
      thresholds = {
          'CD3': 0.5,
          'CD4': 0.4,
          'CD8': 0.4,
          'CD20': 0.3,
          'CD68': 0.3,
          'panCK': 0.5
      }
      
      adata.obs['cell_type'] = annotate_cell_types(adata, thresholds)
      
      # Visualize cell type composition
      import matplotlib.pyplot as plt
      cell_type_counts = adata.obs['cell_type'].value_counts()
      plt.figure(figsize=(10, 6))
      cell_type_counts.plot(kind='bar')
      plt.xlabel('Cell Type')
      plt.ylabel('Count')
      plt.title('Cell Type Composition')
      plt.xticks(rotation=45)
      plt.tight_layout()
      plt.show()
      ```
      
      ### Clustering
      
      Unsupervised clustering to identify cell populations:
      
      ```python
      import scanpy as sc
      
      # Preprocessing for clustering
      sc.pp.normalize_total(adata, target_sum=1e4)
      sc.pp.log1p(adata)
      sc.pp.scale(adata, max_value=10)
      
      # PCA
      sc.tl.pca(adata, n_comps=50)
      
      # Neighborhood graph
      sc.pp.neighbors(adata, n_neighbors=15, n_pcs=30)
      
      # UMAP embedding
      sc.tl.umap(adata)
      
      # Leiden clustering
      sc.tl.leiden(adata, resolution=0.5)
      
      # Visualize
      sc.pl.umap(adata, color=['leiden', 'CD3', 'CD8', 'CD20', 'panCK'])
      ```
      
      ### Spatial Visualization
      
      Visualize cells in spatial context:
      
      ```python
      import matplotlib.pyplot as plt
      
      # Spatial scatter plot
      fig, ax = plt.subplots(figsize=(15, 15))
      
      # Color by cell type
      cell_types = adata.obs['cell_type'].unique()
      colors = plt.cm.tab10(np.linspace(0, 1, len(cell_types)))
      
      for i, cell_type in enumerate(cell_types):
          mask = adata.obs['cell_type'] == cell_type
          coords = adata.obsm['spatial'][mask]
          ax.scatter(
              coords[:, 0],
              coords[:, 1],
              c=[colors[i]],
              label=cell_type,
              s=5,
              alpha=0.7
          )
      
      ax.legend(markerscale=2)
      ax.set_xlabel('X (pixels)')
      ax.set_ylabel('Y (pixels)')
      ax.set_title('Spatial Cell Type Distribution')
      ax.axis('equal')
      plt.tight_layout()
      plt.show()
      ```
      
      ### Spatial Neighborhood Analysis
      
      Analyze cell neighborhoods and interactions:
      
      ```python
      import squidpy as sq
      
      # Calculate spatial neighborhood enrichment
      sq.gr.spatial_neighbors(adata, coord_type='generic', spatial_key='spatial')
      
      # Neighborhood enrichment test
      sq.gr.nhood_enrichment(adata, cluster_key='cell_type')
      
      # Visualize interaction matrix
      sq.pl.nhood_enrichment(adata, cluster_key='cell_type')
      
      # Co-occurrence score
      sq.gr.co_occurrence(adata, cluster_key='cell_type')
      sq.pl.co_occurrence(
          adata,
          cluster_key='cell_type',
          clusters=['CD8 T cell', 'Tumor'],
          figsize=(8, 8)
      )
      ```
      
      ### Spatial Autocorrelation
      
      Test for spatial clustering of markers:
      
      ```python
      # Moran's I spatial autocorrelation
      sq.gr.spatial_autocorr(
          adata,
          mode='moran',
          genes=['CD3', 'CD8', 'PD1', 'PDL1', 'panCK']
      )
      
      # Visualize
      results = adata.uns['moranI']
      print(results.head())
      ```
      
      ## MERFISH and Other Platforms
      
      PathML's most fully supported multiparametric workflows are CODEX and Vectra. MERFISH and related spatial-transcriptomics platforms do not have dedicated decoding/transcript-assignment transforms in the core `pathml.preprocessing` API at the time of writing, so do NOT assume classes like `MERFISHSlide`, `DecodeMERFISH`, or `AssignTranscripts` exist — verify against your installed version's API docs first.
      
      A practical pattern for MERFISH-style data:
      - Load the multi-channel image as a `MultiparametricSlide` (Bio-Formats backend).
      - Segment cells with `SegmentMIF` (Mesmer) on the nuclear + a boundary channel (e.g. poly(T)).
      - For barcode decoding and transcript-to-cell assignment, use a dedicated spatial-transcriptomics toolkit (e.g. a vendor pipeline or `squidpy`/`starfish`) and bring the resulting cell-by-gene matrix back into AnnData for downstream analysis.
      
      ## Quality Control
      
      ### Segmentation Quality
      
      Inspect the instance segmentation mask directly with scikit-image (no PathML-specific QC helper is needed):
      
      ```python
      import numpy as np
      import matplotlib.pyplot as plt
      from skimage.measure import regionprops
      
      # segmentation_mask: integer label image (each cell a unique id)
      props = regionprops(segmentation_mask)
      cell_sizes = np.array([p.area for p in props])
      
      print(f"Total cells: {len(cell_sizes)}")
      print(f"Mean cell size: {cell_sizes.mean():.1f} pixels")
      
      plt.hist(cell_sizes, bins=50)
      plt.xlabel("Cell Size (pixels)")
      plt.ylabel("Frequency")
      plt.title("Cell Size Distribution")
      plt.show()
      ```
      
      ### Marker Expression QC
      
      ```python
      import scanpy as sc
      
      # Load AnnData
      adata = ad.read_h5ad('codex_dataset.h5ad')
      
      # Calculate QC metrics
      adata.obs['total_intensity'] = adata.X.sum(axis=1)
      adata.obs['n_markers_detected'] = (adata.X > 0).sum(axis=1)
      
      # Filter low-quality cells
      adata = adata[adata.obs['total_intensity'] > 100, :]
      adata = adata[adata.obs['n_markers_detected'] >= 3, :]
      
      # Visualize
      sc.pl.violin(adata, ['total_intensity', 'n_markers_detected'], multi_panel=True)
      ```
      
      ## Batch Processing
      
      Process large multiparametric datasets efficiently:
      
      ```python
      from pathml.core import SlideDataset
      from pathml.preprocessing import Pipeline
      from dask.distributed import Client
      import glob
      
      # Start Dask cluster
      client = Client(n_workers=16, threads_per_worker=2, memory_limit='8GB')
      
      # Find all CODEX slides
      slide_dirs = glob.glob('data/codex_slides/*/')
      
      # Create dataset
      codex_slides = [CODEXSlide(d, stain='IF') for d in slide_dirs]
      dataset = SlideDataset(codex_slides)
      
      # Run pipeline in parallel (pass the Dask client to run())
      dataset.run(codex_pipeline, client=client)
      
      # Save the per-slide AnnData (each on slide.counts)
      for i, slide in enumerate(dataset.slides):
          slide.counts.write(f"processed/slide_{i}.h5ad")
      
      client.close()
      ```
      
      ## Integration with Other Tools
      
      ### Export to Spatial Analysis Tools
      
      ```python
      # Export to Giotto
      def export_to_giotto(adata, output_dir):
          import os
          os.makedirs(output_dir, exist_ok=True)
      
          # Expression matrix
          pd.DataFrame(
              adata.X.T,
              index=adata.var_names,
              columns=adata.obs_names
          ).to_csv(f'{output_dir}/expression.csv')
      
          # Cell coordinates
          pd.DataFrame(
              adata.obsm['spatial'],
              columns=['x', 'y'],
              index=adata.obs_names
          ).to_csv(f'{output_dir}/spatial_locs.csv')
      
      # Export to Seurat
      def export_to_seurat(adata, output_file):
          adata.write_h5ad(output_file)
          # Read in R with: library(Seurat); ReadH5AD(output_file)
      ```
      
      ## Best Practices
      
      1. **Channel selection for segmentation:**
         - Use brightest, most consistent nuclear marker (usually DAPI)
         - Choose membrane/cytoplasm marker based on tissue type
         - Test multiple options to optimize segmentation
      
      2. **Background subtraction:**
         - Apply before quantification to reduce autofluorescence
         - Use blank/control images to model background
      
      3. **Quality control:**
         - Visualize segmentation on sample regions
         - Check cell size distributions for outliers
         - Validate marker expression ranges
      
      4. **Cell type annotation:**
         - Start with canonical markers (CD3, CD20, panCK)
         - Use multiple markers for robust classification
         - Consider unsupervised clustering to discover populations
      
      5. **Spatial analysis:**
         - Account for tissue architecture (epithelium, stroma, etc.)
         - Consider local density when interpreting interactions
         - Use permutation tests for statistical significance
      
      6. **Batch effects:**
         - Include batch information in AnnData.obs
         - Apply batch correction if combining multiple experiments
         - Visualize batch effects with UMAP colored by batch
      
      ## Common Issues and Solutions
      
      **Issue: Poor segmentation quality**
      - Verify nuclear and cytoplasm channels are correctly specified
      - Adjust image_resolution parameter to match actual resolution
      - Try different cytoplasm markers
      - Manually tune min/max cell size parameters
      
      **Issue: Low marker intensity**
      - Check for background subtraction artifacts
      - Verify channel names match actual channels
      - Inspect raw images for technical issues (focus, exposure)
      
      **Issue: Cell type annotations don't match expectations**
      - Adjust marker thresholds (too high/low)
      - Visualize marker distributions to set data-driven thresholds
      - Check for antibody specificity issues
      
      **Issue: Spatial analysis shows no significant interactions**
      - Increase neighborhood radius
      - Check for sufficient cell numbers per type
      - Verify spatial coordinates are correctly scaled
      
      ## Additional Resources
      
      - **PathML Multiparametric API:** https://pathml.readthedocs.io/en/latest/api_preprocessing_reference.html
      - **CODEX:** https://www.akoyabio.com/codex/
      - **Vectra:** https://www.akoyabio.com/phenoimager/instruments/vectra-3-0/
      - **DeepCell Mesmer:** https://www.deepcell.org/
      - **Scanpy:** https://scanpy.readthedocs.io/ (single-cell analysis)
      - **Squidpy:** https://squidpy.readthedocs.io/ (spatial omics analysis)
      
    • preprocessing.md 18.8 KB
      # Preprocessing Pipelines & Transforms
      
      ## Overview
      
      PathML provides a modular preprocessing architecture based on composable transforms organized into pipelines. Transforms are individual operations that modify images, create masks, or extract features. Pipelines chain transforms together to create reproducible, scalable preprocessing workflows for computational pathology.
      
      ## Pipeline Architecture
      
      ### Pipeline Class
      
      The `Pipeline` class composes a sequence of transforms applied consecutively:
      
      A `Pipeline` is a reusable, serializable list of transforms. You apply it by passing it to a slide's or dataset's `run()` method (the slide/dataset drives execution, not the pipeline):
      
      ```python
      from pathml.preprocessing import Pipeline, Transform1, Transform2
      
      # Create pipeline
      pipeline = Pipeline([
          Transform1(param1=value1),
          Transform2(param2=value2),
          # ... more transforms
      ])
      
      # Run on a single slide
      slide_data.run(pipeline)
      
      # Run on a dataset (parallelized via a Dask client)
      dataset.run(pipeline, client=client)
      ```
      
      **Key features:**
      - Sequential execution of transforms
      - Automatic handling of tiles and masks
      - Distributed processing support with Dask
      - Reproducible workflows with serializable configuration
      
      ### Transform Base Class
      
      All transforms inherit from the `Transform` base class and implement:
      - `apply()` - Core transformation logic
      - `input_type` - Expected input (tile, mask, etc.)
      - `output_type` - Produced output
      
      ## Transform Categories
      
      PathML provides transforms in six major categories:
      
      1. **Image Modification** - Blur, rescale, histogram equalization
      2. **Mask Creation** - Tissue detection, nucleus detection, thresholding
      3. **Mask Modification** - Morphological operations on masks
      4. **Stain Processing** - H&E stain normalization and separation
      5. **Quality Control** - Artifact detection, white space labeling
      6. **Specialized** - Multiparametric imaging, cell segmentation
      
      ## Image Modification Transforms
      
      ### Blur Operations
      
      Apply various blurring kernels for noise reduction:
      
      **MedianBlur:**
      ```python
      from pathml.preprocessing import MedianBlur
      
      # Apply median filter
      transform = MedianBlur(kernel_size=5)
      ```
      - Effective for salt-and-pepper noise
      - Preserves edges better than Gaussian blur
      
      **GaussianBlur:**
      ```python
      from pathml.preprocessing import GaussianBlur
      
      # Apply Gaussian blur
      transform = GaussianBlur(kernel_size=5, sigma=1.0)
      ```
      - Smooth noise reduction
      - Adjustable sigma controls blur strength
      
      **BoxBlur:**
      ```python
      from pathml.preprocessing import BoxBlur
      
      # Apply box filter
      transform = BoxBlur(kernel_size=5)
      ```
      - Fastest blur operation
      - Uniform averaging within kernel
      
      ### Intensity Adjustments
      
      **RescaleIntensity:**
      ```python
      from pathml.preprocessing import RescaleIntensity
      
      # Rescale intensity to [0, 255]
      transform = RescaleIntensity(
          in_range=(0, 1.0),
          out_range=(0, 255)
      )
      ```
      
      **HistogramEqualization:**
      ```python
      from pathml.preprocessing import HistogramEqualization
      
      # Global histogram equalization
      transform = HistogramEqualization()
      ```
      - Enhances global contrast
      - Spreads out intensity distribution
      
      **AdaptiveHistogramEqualization (CLAHE):**
      ```python
      from pathml.preprocessing import AdaptiveHistogramEqualization
      
      # Contrast Limited Adaptive Histogram Equalization
      transform = AdaptiveHistogramEqualization(
          clip_limit=0.03,
          tile_grid_size=(8, 8)
      )
      ```
      - Enhances local contrast
      - Prevents over-amplification with clip_limit
      - Better for images with varying local contrast
      
      ### Superpixel Processing
      
      **SuperpixelInterpolation:**
      ```python
      from pathml.preprocessing import SuperpixelInterpolation
      
      # Divide into superpixels using SLIC
      transform = SuperpixelInterpolation(
          n_segments=100,
          compactness=10.0
      )
      ```
      - Segments image into perceptually meaningful regions
      - Useful for feature extraction and segmentation
      
      ## Mask Creation Transforms
      
      ### H&E Tissue and Nucleus Detection
      
      **TissueDetectionHE:**
      ```python
      from pathml.preprocessing import TissueDetectionHE
      
      # Detect tissue regions in H&E slides
      transform = TissueDetectionHE(
          use_saturation=True,  # Use HSV saturation channel
          threshold=10,  # Intensity threshold
          min_region_size=500  # Minimum tissue region size in pixels
      )
      ```
      - Creates binary tissue mask
      - Filters small regions and artifacts
      - Stores mask in `tile.masks['tissue']`
      
      **NucleusDetectionHE:**
      ```python
      from pathml.preprocessing import NucleusDetectionHE
      
      # Detect nuclei in H&E images
      transform = NucleusDetectionHE(
          stain='hematoxylin',  # Use hematoxylin channel
          threshold=0.3,
          min_nucleus_size=10
      )
      ```
      - Separates hematoxylin stain
      - Thresholds to create nucleus mask
      - Stores mask in `tile.masks['nucleus']`
      
      ### Binary Thresholding
      
      **BinaryThreshold:**
      ```python
      from pathml.preprocessing import BinaryThreshold
      
      # Threshold using Otsu's method
      transform = BinaryThreshold(
          method='otsu',  # 'otsu' or manual threshold value
          invert=False
      )
      
      # Or specify manual threshold
      transform = BinaryThreshold(threshold=128)
      ```
      
      ### Foreground Detection
      
      **ForegroundDetection:**
      ```python
      from pathml.preprocessing import ForegroundDetection
      
      # Detect foreground regions
      transform = ForegroundDetection(
          threshold=0.5,
          min_region_size=1000,  # Minimum size in pixels
          use_saturation=True
      )
      ```
      
      ## Mask Modification Transforms
      
      Apply morphological operations to clean up masks:
      
      **MorphOpen:**
      ```python
      from pathml.preprocessing import MorphOpen
      
      # Remove small objects and noise
      transform = MorphOpen(
          kernel_size=5,
          mask_name='tissue'  # Which mask to modify
      )
      ```
      - Erosion followed by dilation
      - Removes small objects and noise
      
      **MorphClose:**
      ```python
      from pathml.preprocessing import MorphClose
      
      # Fill small holes
      transform = MorphClose(
          kernel_size=5,
          mask_name='tissue'
      )
      ```
      - Dilation followed by erosion
      - Fills small holes in mask
      
      ## Stain Normalization
      
      ### StainNormalizationHE
      
      Normalize H&E staining across slides to account for variations in staining procedure and scanners:
      
      ```python
      from pathml.preprocessing import StainNormalizationHE
      
      # Normalize to reference stain vectors
      transform = StainNormalizationHE(
          target="normalize",  # "normalize", "hematoxylin", or "eosin"
          stain_estimation_method="macenko",  # "macenko" or "vahadane"
      )
      ```
      
      **Target modes:**
      - `"normalize"` - Normalize both stains to reference
      - `"hematoxylin"` - Extract hematoxylin channel only
      - `"eosin"` - Extract eosin channel only
      
      **Stain estimation methods:**
      - `"macenko"` - Macenko et al. 2009 method (faster, more stable)
      - `"vahadane"` - Vahadane et al. 2016 method (more accurate, slower)
      
      **Other documented parameters** (confirm against the API docs for your version):
      ```python
      transform = StainNormalizationHE(
          target="normalize",
          stain_estimation_method="macenko",
          optical_density_threshold=0.15,   # OD cutoff separating tissue from background
          angular_percentile=0.01,          # robust percentile for stain-vector estimation
          regularizer=0.01,                 # regularization for the vahadane method
          background_intensity=245,          # assumed background intensity
          stain_matrix_target_od=None,       # reference stain matrix in OD space (optional)
          max_c_target=None,                 # reference max stain concentrations (optional)
      )
      ```
      
      Note: `StainNormalizationHE` does not take a `tissue_mask_name` argument; it estimates stain vectors from the tile's optical-density distribution.
      
      **Workflow:**
      1. Convert RGB to optical density (OD)
      2. Estimate stain matrix (H&E vectors)
      3. Decompose into stain concentrations
      4. Normalize to reference stain distribution
      5. Reconstruct normalized RGB image
      
      **Example: detect tissue, then normalize:**
      ```python
      from pathml.preprocessing import Pipeline, TissueDetectionHE, StainNormalizationHE
      
      pipeline = Pipeline([
          TissueDetectionHE(),  # creates the 'tissue' mask
          StainNormalizationHE(
              target="normalize",
              stain_estimation_method="macenko",
          ),
      ])
      ```
      
      ## Quality Control Transforms
      
      ### Artifact Detection
      
      **LabelArtifactTileHE:**
      ```python
      from pathml.preprocessing import LabelArtifactTileHE
      
      # Label tiles containing artifacts
      transform = LabelArtifactTileHE(
          pen_threshold=0.5,  # Threshold for pen marking detection
          bubble_threshold=0.5  # Threshold for bubble detection
      )
      ```
      - Detects pen markings, bubbles, and other artifacts
      - Labels affected tiles for filtering
      
      **LabelWhiteSpaceHE:**
      ```python
      from pathml.preprocessing import LabelWhiteSpaceHE
      
      # Label tiles with excessive white space
      transform = LabelWhiteSpaceHE(
          threshold=0.9,  # Fraction of white pixels
          mask_name='white_space'
      )
      ```
      - Identifies tiles with mostly background
      - Useful for filtering uninformative tiles
      
      ## Multiparametric Imaging Transforms
      
      ### Cell Segmentation
      
      **SegmentMIF:**
      ```python
      from pathml.preprocessing import SegmentMIF
      
      # Segment cells using the DeepCell Mesmer model.
      # nuclear_channel / cytoplasm_channel are INTEGER indices into the
      # (collapsed) channel stack, not marker name strings.
      transform = SegmentMIF(
          model="mesmer",
          nuclear_channel=0,        # e.g. index of DAPI
          cytoplasm_channel=29,     # e.g. index of a membrane/cytoplasm marker
          image_resolution=0.377,   # microns per pixel
      )
      ```
      - Uses the DeepCell Mesmer model for whole-cell + nuclear segmentation
      - Channels are specified by integer index into the channel stack
      - Writes `cell_segmentation` and `nuclear_segmentation` masks
      - Fine-grained control is via `preprocess_kwargs`, `postprocess_kwargs_nuclear`, and `postprocess_kwargs_whole_cell` (there is no `compartment` argument)
      
      **SegmentMIFRemote:**
      ```python
      from pathml.preprocessing import SegmentMIFRemote
      
      # Remote inference using the DeepCell Kiosk API (no local GPU)
      transform = SegmentMIFRemote(
          model="mesmer",
          nuclear_channel=0,
          cytoplasm_channel=29,
      )
      ```
      - Same functionality as SegmentMIF but offloads inference to the DeepCell service
      - No local GPU required; suitable for batch processing
      
      ### Marker Quantification
      
      **QuantifyMIF:**
      ```python
      from pathml.preprocessing import QuantifyMIF
      
      # Quantify marker expression per cell. The only argument is the name of
      # the segmentation mask produced by SegmentMIF.
      transform = QuantifyMIF(segmentation_mask="cell_segmentation")
      ```
      - Extracts mean marker intensity per segmented cell across all channels
      - Computes per-cell spatial/morphology info
      - Writes an AnnData object to `slide.counts` for downstream single-cell analysis (read it back via `slide.counts`, or concatenate across a dataset with `anndata.concat([s.counts for s in dataset.slides], ...)`)
      
      ### CODEX/Vectra Specific
      
      **CollapseRunsCODEX:**
      ```python
      from pathml.preprocessing import CollapseRunsCODEX
      
      # Consolidate a multi-cycle CODEX z-stack by selecting a focal plane.
      transform = CollapseRunsCODEX(z=0)  # 'z' selects the z-plane index
      ```
      - Collapses the CODEX z-stack/cycles into a single multi-channel image
      - `z` selects the focal plane (0-indexed)
      
      **CollapseRunsVectra:**
      ```python
      from pathml.preprocessing import CollapseRunsVectra
      
      # Process Vectra multiplex IF data
      transform = CollapseRunsVectra(
          wavelengths=[520, 570, 620, 670, 780]  # Emission wavelengths
      )
      ```
      
      ## Building Comprehensive Pipelines
      
      ### Basic H&E Preprocessing Pipeline
      
      ```python
      from pathml.preprocessing import (
          Pipeline,
          TissueDetectionHE,
          StainNormalizationHE,
          NucleusDetectionHE,
          MedianBlur,
          LabelWhiteSpaceHE
      )
      
      pipeline = Pipeline([
          # 1. Quality control
          LabelWhiteSpaceHE(threshold=0.9),
      
          # 2. Noise reduction
          MedianBlur(kernel_size=3),
      
          # 3. Tissue detection
          TissueDetectionHE(min_region_size=500),
      
          # 4. Stain normalization
          StainNormalizationHE(
              target='normalize',
              stain_estimation_method='macenko',
              tissue_mask_name='tissue'
          ),
      
          # 5. Nucleus detection
          NucleusDetectionHE(threshold=0.3)
      ])
      ```
      
      ### CODEX Multiparametric Pipeline
      
      ```python
      from pathml.preprocessing import (
          Pipeline,
          CollapseRunsCODEX,
          SegmentMIF,
          QuantifyMIF
      )
      
      codex_pipeline = Pipeline([
          # 1. Collapse the z-stack
          CollapseRunsCODEX(z=0),
      
          # 2. Cell segmentation (integer channel indices)
          SegmentMIF(
              model="mesmer",
              nuclear_channel=0,
              cytoplasm_channel=29,
              image_resolution=0.377,
          ),
      
          # 3. Quantify markers -> writes AnnData to slide.counts
          QuantifyMIF(segmentation_mask="cell_segmentation"),
      ])
      ```
      
      ### Advanced Pipeline with Quality Control
      
      ```python
      from pathml.preprocessing import (
          Pipeline,
          LabelWhiteSpaceHE,
          LabelArtifactTileHE,
          TissueDetectionHE,
          MorphOpen,
          MorphClose,
          StainNormalizationHE,
          AdaptiveHistogramEqualization
      )
      
      advanced_pipeline = Pipeline([
          # Stage 1: Quality control
          LabelWhiteSpaceHE(threshold=0.85),
          LabelArtifactTileHE(pen_threshold=0.5, bubble_threshold=0.5),
      
          # Stage 2: Tissue detection
          TissueDetectionHE(threshold=10, min_region_size=1000),
          MorphOpen(kernel_size=5, mask_name='tissue'),
          MorphClose(kernel_size=7, mask_name='tissue'),
      
          # Stage 3: Stain normalization
          StainNormalizationHE(
              target='normalize',
              stain_estimation_method='vahadane',
              tissue_mask_name='tissue'
          ),
      
          # Stage 4: Contrast enhancement
          AdaptiveHistogramEqualization(clip_limit=0.03, tile_grid_size=(8, 8))
      ])
      ```
      
      ## Running Pipelines
      
      ### Single Slide Processing
      
      ```python
      from pathml.core import HESlide
      
      # Load slide
      wsi = HESlide("slide.svs")
      
      # Run the pipeline. SlideData.run handles tiling internally; pass tiling
      # options through (e.g. tile_size / level) as supported by your version.
      wsi.run(pipeline, tile_size=256, tile_stride=256, level=1)
      
      # Access processed data
      for tile in wsi.tiles:
          normalized_image = tile.image
          tissue_mask = tile.masks.get("tissue")
          nucleus_mask = tile.masks.get("nucleus")
      ```
      
      ### Batch Processing with Distributed Execution
      
      ```python
      from pathml.core import HESlide, SlideDataset
      from dask.distributed import Client
      import glob
      
      # Start Dask client
      client = Client(n_workers=8, threads_per_worker=2, memory_limit="4GB")
      
      # Create dataset from slide objects
      slide_paths = glob.glob("data/*.svs")
      dataset = SlideDataset([HESlide(p) for p in slide_paths])
      
      # Run pipeline in parallel (tiling options passed through to run())
      dataset.run(pipeline, client=client, tile_size=512, tile_stride=512, level=1)
      
      # Persist results to h5path (one file per slide in the directory)
      dataset.write("processed/")
      
      client.close()
      ```
      
      ### Filtering Tiles After Processing
      
      Filter on masks produced by the pipeline (e.g. keep only tissue tiles):
      
      ```python
      wsi.run(pipeline, tile_size=256, level=1)
      
      tissue_tiles = [
          tile for tile in wsi.tiles
          if tile.masks.get("tissue") is not None and tile.masks["tissue"].any()
      ]
      ```
      
      ## Performance Optimization
      
      ### Memory Management
      
      ```python
      from pathml.core import HESlide, SlideDataset
      
      # Process large datasets in batches
      batch_size = 100
      for i in range(0, len(slide_paths), batch_size):
          batch_paths = slide_paths[i:i + batch_size]
          batch_dataset = SlideDataset([HESlide(p) for p in batch_paths])
          batch_dataset.run(pipeline, client=client)
          batch_dataset.write(f"processed/batch_{i}/")
      ```
      
      ### GPU Acceleration
      
      Certain transforms leverage GPU acceleration when available:
      
      ```python
      import torch
      
      # Check GPU availability
      print(f"CUDA available: {torch.cuda.is_available()}")
      
      # Transforms that benefit from GPU:
      # - SegmentMIF (Mesmer deep learning model)
      # - StainNormalizationHE (matrix operations)
      ```
      
      ### Parallel Workers Configuration
      
      ```python
      from dask.distributed import Client
      
      # CPU-bound tasks (image processing)
      client = Client(
          n_workers=8,
          threads_per_worker=1,  # Use processes, not threads
          memory_limit='8GB'
      )
      
      # GPU tasks (deep learning inference)
      client = Client(
          n_workers=2,  # Fewer workers for GPU
          threads_per_worker=4,
          processes=True
      )
      ```
      
      ## Custom Transforms
      
      Create custom preprocessing operations by subclassing `Transform`:
      
      ```python
      from pathml.preprocessing.transforms import Transform
      import numpy as np
      
      class CustomTransform(Transform):
          def __init__(self, param1, param2):
              self.param1 = param1
              self.param2 = param2
      
          def apply(self, tile):
              # Access tile image
              image = tile.image
      
              # Apply custom operation
              processed = self.custom_operation(image, self.param1, self.param2)
      
              # Update tile
              tile.image = processed
      
              return tile
      
          def custom_operation(self, image, param1, param2):
              # Implement custom logic
              return processed_image
      
      # Use in pipeline
      pipeline = Pipeline([
          CustomTransform(param1=10, param2=0.5),
          # ... other transforms
      ])
      ```
      
      ## Best Practices
      
      1. **Order transforms appropriately:**
         - Quality control first (LabelWhiteSpace, LabelArtifact)
         - Noise reduction early (Blur)
         - Tissue detection before stain normalization
         - Stain normalization before color-dependent operations
      
      2. **Use tissue masks for stain normalization:**
         - Improves accuracy by excluding background
         - `TissueDetectionHE()` then `StainNormalizationHE(tissue_mask_name='tissue')`
      
      3. **Apply morphological operations to clean masks:**
         - `MorphOpen` to remove small false positives
         - `MorphClose` to fill small gaps
      
      4. **Leverage distributed processing for large datasets:**
         - Use Dask for parallel execution
         - Configure workers based on available resources
      
      5. **Save intermediate results:**
         - Store processed data to HDF5 for reuse
         - Avoid reprocessing computationally expensive transforms
      
      6. **Validate preprocessing on sample images:**
         - Visualize intermediate steps
         - Tune parameters on representative samples before batch processing
      
      7. **Handle edge cases:**
         - Check for empty masks before downstream operations
         - Validate tile quality before expensive computations
      
      ## Common Issues and Solutions
      
      **Issue: Stain normalization produces artifacts**
      - Use tissue mask to exclude background
      - Try different stain estimation method (macenko vs. vahadane)
      - Verify optical density parameters match your images
      
      **Issue: Out of memory during pipeline execution**
      - Reduce number of Dask workers
      - Decrease tile size
      - Process images at lower pyramid level
      - Enable memory_limit parameter in Dask client
      
      **Issue: Tissue detection misses tissue regions**
      - Adjust threshold parameter
      - Use saturation channel: `use_saturation=True`
      - Reduce min_region_size to capture smaller tissue fragments
      
      **Issue: Nucleus detection is inaccurate**
      - Verify stain separation quality (visualize hematoxylin channel)
      - Adjust threshold parameter
      - Apply stain normalization before nucleus detection
      
      ## Additional Resources
      
      - **PathML Preprocessing API:** https://pathml.readthedocs.io/en/latest/api_preprocessing_reference.html
      - **Stain Normalization Methods:**
        - Macenko et al. 2009: "A method for normalizing histology slides for quantitative analysis"
        - Vahadane et al. 2016: "Structure-Preserving Color Normalization and Sparse Stain Separation"
      - **DeepCell Mesmer:** https://www.deepcell.org/ (cell segmentation model)
      
  • SKILL.md 9.4 KB
    ---
    name: alterlab-pathml
    description: Run full computational-pathology workflows with PathML — whole-slide-image (WSI) analysis across 160+ slide formats, multiplexed immunofluorescence (CODEX, Vectra, MERFISH), nucleus segmentation/classification (HoVer-Net, HACTNet), tissue- and cell-graph construction, HDF5 dataset management, and deep-learning model training on pathology data. Use when the user builds end-to-end deep-learning pathology pipelines, analyzes multiplexed or spatial-proteomics slides, or segments nuclei. For lightweight H&E slide preprocessing, tissue masking, or plain Random/Grid/Score tile extraction prefer alterlab-histolab instead. Part of the AlterLab Academic Skills suite.
    license: GPL-2.0
    allowed-tools: Read Write Edit Bash(python:*) Bash(uv:*)
    compatibility: "Runs under `uv run python`, but not in a shared environment: PathML 3.0.x (current 3.0.8, released 2026-08) hard-pins an older scientific stack (numpy<2, pandas<=2.1.4, scanpy==1.9.6, anndata<=0.10.3, torch==2.12.0) and needs native OpenSlide plus a JDK for Bio-Formats. Give it a dedicated environment. No API key or account required."
    metadata:
        skill-author: AlterLab
        version: "1.1.0"
        last_updated: "2026-09-23"
    ---
    
    # PathML
    
    ## Overview
    
    PathML is a comprehensive Python toolkit for computational pathology workflows, designed to facilitate machine learning and image analysis for whole-slide pathology images. The framework provides modular, composable tools for loading diverse slide formats, preprocessing images, constructing spatial graphs, training deep learning models, and analyzing multiparametric imaging data from technologies like CODEX and multiplex immunofluorescence.
    
    ## When to Use This Skill
    
    Apply this skill for:
    - Loading and processing whole-slide images (WSI) in various proprietary formats
    - Preprocessing H&E stained tissue images with stain normalization
    - Nucleus detection, segmentation, and classification workflows
    - Building cell and tissue graphs for spatial analysis
    - Training or deploying machine learning models (HoVer-Net, HACTNet) on pathology data
    - Analyzing multiparametric imaging (CODEX, Vectra, MERFISH) for spatial proteomics
    - Quantifying marker expression from multiplex immunofluorescence
    - Managing large-scale pathology datasets with HDF5 storage
    - Tile-based analysis and stitching operations
    
    ### Does NOT Trigger
    
    | Scenario | Use Instead |
    |----------|-------------|
    | Lightweight H&E preprocessing, tissue masking, Random/Grid/Score tile extraction | `alterlab-histolab` |
    | Spatial transcriptomics neighborhood stats on Visium/Xenium/MERFISH tables | `alterlab-squidpy-spatial` |
    | Single-cell expression analysis of the resulting cell x marker matrix | `alterlab-scanpy` |
    | Training a general vision model with no pathology-specific I/O or transforms | `alterlab-pytorch-lightning` |
    | Graph neural networks on a graph you already built | `alterlab-torch-geometric` |
    
    ## Core Capabilities
    
    PathML provides six major capability areas documented in detail within reference files:
    
    ### 1. Image Loading & Formats
    
    Load whole-slide images from 160+ proprietary formats including Aperio SVS, Hamamatsu NDPI, Leica SCN, Zeiss ZVI, DICOM, and OME-TIFF. PathML automatically handles vendor-specific formats and provides unified interfaces for accessing image pyramids, metadata, and regions of interest.
    
    **See:** `references/image_loading.md` for supported formats, loading strategies, and working with different slide types.
    
    ### 2. Preprocessing Pipelines
    
    Build modular preprocessing pipelines by composing transforms for image manipulation, quality control, stain normalization, tissue detection, and mask operations. PathML's Pipeline architecture enables reproducible, scalable preprocessing across large datasets.
    
    **Key transforms:**
    - `StainNormalizationHE` - Macenko/Vahadane stain normalization
    - `TissueDetectionHE`, `NucleusDetectionHE` - Tissue/nucleus segmentation
    - `MedianBlur`, `GaussianBlur` - Noise reduction
    - `LabelArtifactTileHE` - Quality control for artifacts
    
    **See:** `references/preprocessing.md` for complete transform catalog, pipeline construction, and preprocessing workflows.
    
    ### 3. Graph Construction
    
    Construct spatial graphs representing cellular and tissue-level relationships. Extract features from segmented objects to create graph-based representations suitable for graph neural networks and spatial analysis.
    
    **See:** `references/graphs.md` for graph construction methods, feature extraction, and spatial analysis workflows.
    
    ### 4. Machine Learning
    
    Train and deploy deep learning models for nucleus detection, segmentation, and classification. PathML integrates PyTorch with pre-built models (HoVer-Net, HACTNet), custom DataLoaders, and ONNX support for inference.
    
    **Key models:**
    - **HoVer-Net** - Simultaneous nucleus segmentation and classification
    - **HACTNet** - Hierarchical cell-type classification
    
    **See:** `references/machine_learning.md` for model training, evaluation, inference workflows, and working with public datasets.
    
    ### 5. Multiparametric Imaging
    
    Analyze spatial proteomics and gene expression data from CODEX, Vectra, MERFISH, and other multiplex imaging platforms. PathML provides specialized slide classes and transforms for processing multiparametric data, cell segmentation with Mesmer, and quantification workflows.
    
    **See:** `references/multiparametric.md` for CODEX/Vectra workflows, cell segmentation, marker quantification, and integration with AnnData.
    
    ### 6. Data Management
    
    Efficiently store and manage large pathology datasets using HDF5 format. PathML handles tiles, masks, metadata, and extracted features in unified storage structures optimized for machine learning workflows.
    
    **See:** `references/data_management.md` for HDF5 integration, tile management, dataset organization, and batch processing strategies.
    
    ## Quick Start
    
    ### Installation
    
    PathML needs native dependencies present first — **OpenSlide** and a **JDK** (Bio-Formats is
    driven through JPype/javabridge) — so the maintainers recommend a conda environment; pure-pip
    installs commonly fail on those native deps.
    
    Give PathML its own environment, because 3.0.8 pins much of the scientific stack to exact or
    upper-bounded versions:
    
    | Pinned by PathML 3.0.8 | Current elsewhere |
    |---|---|
    | `numpy<2` | 2.5.x |
    | `pandas<=2.1.4` | 3.0.x |
    | `scanpy==1.9.6`, `anndata<=0.10.3` | scanpy 1.12.x, anndata 0.13.x |
    | `scikit-image<=0.22.0`, `networkx<=3.2.1`, `h5py==3.10.0` | all newer |
    | `torch==2.12.0`, `torch-geometric==2.8.0` | torch 2.14.x |
    
    Installing PathML next to `alterlab-scanpy` or `alterlab-squidpy-spatial` will either fail to
    resolve or silently downgrade those skills' stack. Move results between environments as files
    (HDF5/AnnData written by PathML, read by a current-scanpy env) rather than trying to satisfy
    both pin sets.
    
    ```bash
    # In a dedicated env, with OpenSlide and a JDK already installed.
    uv venv .venv-pathml && source .venv-pathml/bin/activate
    uv pip install pathml
    ```
    
    ### Basic Workflow Example
    
    ```python
    from pathml.core import HESlide
    from pathml.preprocessing import Pipeline, StainNormalizationHE, TissueDetectionHE
    
    # Load a whole-slide image. Use the HESlide convenience class for H&E,
    # or SlideData(filepath=..., slide_type=types.HE) for the generic constructor.
    # (There is no SlideData.from_slide.)
    wsi = HESlide("path/to/slide.svs", name="example")
    
    # Create preprocessing pipeline
    pipeline = Pipeline([
        TissueDetectionHE(),
        StainNormalizationHE(target="normalize", stain_estimation_method="macenko"),
    ])
    
    # Run the pipeline on the slide (SlideData.run handles tiling + transforms)
    wsi.run(pipeline)
    
    # Access processed tiles
    for tile in wsi.tiles:
        processed_image = tile.image
        tissue_mask = tile.masks["tissue"]
    ```
    
    ### Common Workflows
    
    **H&E Image Analysis:**
    1. Load WSI with appropriate slide class
    2. Apply tissue detection and stain normalization
    3. Perform nucleus detection or train segmentation models
    4. Extract features and build spatial graphs
    5. Conduct downstream analysis
    
    **Multiparametric Imaging (CODEX):**
    1. Load CODEX slide with `CODEXSlide`
    2. Collapse multi-run channel data with `CollapseRunsCODEX`
    3. Segment cells using `SegmentMIF` (Mesmer)
    4. Quantify per-cell marker expression with `QuantifyMIF`
    5. Read the resulting AnnData from `slide.counts` for single-cell analysis
    
    **Training ML Models:**
    1. Prepare data with a `pathml.datasets` DataModule (e.g. `PanNukeDataModule`) or a `TileDataset`
    2. Train `HoVerNet` (or another model) with a standard PyTorch loop
    3. Post-process predictions with `post_process_batch_hovernet`
    4. Evaluate on held-out test sets
    5. Optionally export to ONNX for inference
    
    ## Reference Files
    
    Load the relevant reference for detailed API, workflows, and gotchas:
    
    - `references/image_loading.md` - WSI formats, slide classes, loading strategies
    - `references/preprocessing.md` - transform catalog, pipeline construction, stain normalization
    - `references/graphs.md` - graph builders, feature extraction, spatial analysis
    - `references/machine_learning.md` - HoVer-Net/HACTNet, training, datasets, ONNX inference
    - `references/multiparametric.md` - CODEX/Vectra/multiplex IF, cell segmentation, quantification
    - `references/data_management.md` - h5path storage, tile management, batch processing
    
    PathML's API surface shifts between releases; treat the reference code as workflow scaffolding and confirm exact class/method names against the version you have installed (`python -c "import pathml; print(pathml.__version__)"`) and the official API docs at https://pathml.readthedocs.io/.
    
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related