Skip to main content

WSI Toolbox

A comprehensive toolkit for Whole Slide Image (WSI) processing, feature extraction, and clustering analysis.

0.6: progress, preset and device are now passed to commands as arguments (see Python API). Upgrading from 0.5? Read _docs/migration-0.6.md. Changes are listed in CHANGELOG.md.

Installation

# From PyPI
pip install wsi-toolbox

# From GitHub (latest)
pip install git+https://github.com/technoplasm/wsi-toolbox.git

Presets

Tile presets (per-patch feature extractors)

Preset Arch Params Dim HuggingFace
uni ViT-L/16 300M 1024 MahmoodLab/UNI
uni2 (default) ViT-H/14 681M 1536 MahmoodLab/UNI2-h
gigapath ViT-g/14 1.1B 1536 prov-gigapath/prov-gigapath
gigapath-flash ViT-S/16 22M 384 prov-gigapath/prov-gigapath-flash
virchow ViT-H/14 632M 1280 paige-ai/Virchow
virchow2 ViT-H/14 632M 1280 paige-ai/Virchow2
h-optimus-0 ViT-g/14 1.1B 1536 bioptimus/H-optimus-0
conch15 ViT-L/16 300M 1024 MahmoodLab/conchv1_5
conch15_768 ViT-L/16 300M 768 MahmoodLab/conchv1_5
midnight ViT-g/14 1.1B 1536 SophontAI/OpenMidnight
phikon2 ViT-L/16 300M 1024 owkin/phikon-v2

conch15_768 outputs FC-projected features (not cls_token), intended for TITAN input.

Slide presets (slide-level aggregators)

Preset Tile source Dim HuggingFace
titan conch15_768 768 MahmoodLab/TITAN

Preset vs model

  • --preset selects which foundation model to load (e.g. uni, gigapath). Only preset is a session-level default.
  • -M / --model is the HDF5 storage key under which results are written. Defaults to --preset. Use a distinct value to keep multiple extractions of the same preset separate (e.g. different --patch-size).
wt extract -i sample.ndpi --preset uni                          # → uni/features
wt extract -i sample.ndpi --preset uni -M uni_224 -S 224        # → uni_224/features (same preset, 224 px)

Setup: These models require HuggingFace authentication. Accept the license on each model page, then:

huggingface-cli login

GPU Configuration

Device selection is controlled by --device / -D (CLI) or the device= argument of a command (Python). set_default_device() sets the process-wide default that is used when device= is not given. Default is auto.

Value Behavior
auto (default) Detect all GPUs. Multiple GPUs → parallel inference. Single GPU → cuda:0. No GPU → cpu (with warning)
cuda:0 Use GPU 0 only. Falls back to cpu if unavailable (with warning)
cuda:1 Use GPU 1 only
cuda:0,1,3 Use specified GPUs in parallel
cpu CPU only
wt extract -i sample.ndpi -D auto           # Auto-detect (default)
wt extract -i sample.ndpi -D cuda:0         # Single GPU
wt extract -i sample.ndpi -D cuda:0,1       # 2 GPUs in parallel
cmd = wt.FeatureExtractionCommand(model='uni2', preset='uni2', device='cuda:0,1')  # Use GPU 0 and 1
wt.set_default_device('cuda:0,1')  # or: process-wide default for commands called without device=

For the Streamlit app, set via environment variable:

WT_DEVICE=cuda:0 uv run task app

Acceleration (TileEncoder, accel)

TileEncoder holds the loaded tile model(s) and the acceleration choice. Build it once, call warmup(), and pass it to as many FeatureExtractionCommand calls as you like (a long-lived service pays the compile cost once per process). Without encoder= the command builds a temporary encoder per call.

enc = wt.TileEncoder('gigapath-flash', device='cuda', accel='graphs')
enc.warmup()                                   # compiles every bucket shape (~5 s each cold, ~1 s with a warm cache)
cmd = wt.FeatureExtractionCommand(model='gigapath-flash', encoder=enc, batch_size=512)
cmd('a.h5', wsi_path='a.ndpi'); cmd('b.h5', wsi_path='b.ndpi')
enc.close()
wt extract -i sample.ndpi --preset gigapath-flash --accel graphs   # none (default) | compile | graphs
accel What it does
none Eager forward on each reader batch (bit-identical to earlier releases)
compile torch.compile(dynamic=False); every batch is padded to a fixed bucket (64/128/256/512 patches) so there is one graph per shape
graphs The same plus CUDA graphs (mode="reduce-overhead"), which removes the kernel-launch overhead of a small model such as GigaPath-Flash (ViT-S)

Padding repeats the last patch and is sliced off again, so features do not depend on it (cosine to eager

0.9999; the bf16 kernels differ slightly). The padding is wasted compute, so the gain depends on how full the reader's row batches are: exactly filled buckets run 1.5x faster than eager on a GB10, a real slide 1.25-1.3x (_docs/benchmark-pyramid-dzi.md §12.4). Finer buckets (buckets=tuple(range(32, 513, 32))) or a larger batch_size (more rows per batch) fill them better at the cost of more shapes to compile. accel is meant for long-lived processes: a one-shot wt extract --accel graphs pays the warmup inside the run and is slower than eager for a single slide. CUDA only; on the CPU accel falls back to none with a warning. The H5 group attrs and FeatureExtractResult.accel record what ran.

Blackwell / GB10 (DGX Spark): the +cu128 torch wheels have no bf16 Tensor Core GEMM for sm_121 (10.7 vs 97 TFLOPS), so pyproject.toml pins the cu130 index for Linux and uv uses its own managed CPython (python-preference = "only-managed"; torch.compile needs Python.h, which the hosts' system Python lacks).

Quick Start

# 1. Extract features from WSI
wt extract -i sample.ndpi -o sample.h5

# 2. Run clustering
wt cluster -i sample.h5

# 3. Generate preview image (requires sample.ndpi in same directory)
wt preview -i sample.h5
import wsi_toolbox as wt

# Process-wide defaults (optional). Commands read them when preset= / device= are not given.
wt.set_default_preset('uni2')
wt.set_default_device('auto')

# 1. Extract (progress goes to a tqdm bar by default; see "Python API" for sinks and cancellation)
cmd = wt.FeatureExtractionCommand(model='uni2', preset='uni2', batch_size=256)
cmd('sample.h5', wsi_path='sample.ndpi')

# 2. Cluster
cluster_cmd = wt.ClusteringCommand(model='uni2', resolution=1.0)
cluster_cmd(['sample.h5'])

# 3. Preview
preview_cmd = wt.PreviewClustersCommand(model='uni2')
img = preview_cmd('sample.h5')
img.save('sample_preview.jpg')

Important: preview / preview-score commands require the original WSI file with the same stem in the same directory (e.g., sample.h5 needs sample.ndpi).

Python API

Every command follows the same pattern: configuration in __init__, execution in __call__, a Pydantic result object back. In 0.6 the three things a caller may want to control at run time are all arguments:

What Where Fallback when omitted
Foundation model preset= in __init__ (name or TilePreset) wt.set_default_preset(...); ValueError if neither is set
Device device= in __init__ wt.set_default_device(...) (default auto)
Progress display on_progress= in __call__ (keyword-only) wt.set_default_progress(...) (default tqdm)
Cancellation should_cancel= in __call__ (keyword-only) never cancelled

The library never writes the defaults itself; only wt.set_default_* does. Notebooks can set them once, services should pass everything explicitly. The full list of names is in README_API.md.

Progress sinks

Commands do not draw progress bars. They emit wt.ProgressEvent values and a sink — any callable taking one event — decides what to do with them:

@dataclass(frozen=True)
class ProgressEvent:
    phase: str          # "Initializing model", "Processing patches", "UMAP", ...
    n: int              # progress within the phase
    total: int | None   # phase size, None when unknown
    elapsed: float      # seconds since the command started
    message: str = ""   # extra text (batch description etc.)
    done: bool = False  # True only for the final event
    # .fraction -> n / total, or None

Built-in sinks (all importable from wsi_toolbox):

Sink Use
TqdmSink(**tqdm_kwargs) One tqdm bar per phase. Default when nothing is given
RichSink(console=None) rich.progress display; what the CLI uses
StreamlitSink(container=None) st.progress per phase; used by the Streamlit app
LoggingSink(logger=None, every=5.0, level=INFO) phase [n/total] message to a logger, at most once per every seconds
MultiSink(*sinks) Fan one stream out to several sinks
NullSink() Silence. on_progress=None does the same
resolve_sink("tqdm" | "rich" | "streamlit" | "logging" | "none") Name → fresh sink instance
import logging
import wsi_toolbox as wt

cmd = wt.FeatureExtractionCommand(model='uni2', preset='uni2', device='cuda:0')

cmd('sample.h5', wsi_path='sample.ndpi')                                # TqdmSink (default)
cmd('sample.h5', wsi_path='sample.ndpi', on_progress=wt.RichSink())     # rich
cmd('sample.h5', wsi_path='sample.ndpi', on_progress=wt.NullSink())     # silent (or on_progress=None)

# tqdm on the terminal + throttled lines in a service log
sink = wt.MultiSink(wt.TqdmSink(), wt.LoggingSink(logging.getLogger('myservice'), every=5.0))
cmd('sample.h5', wsi_path='sample.ndpi', on_progress=sink)

# Streamlit: draw inside a container
# cmd('sample.h5', on_progress=wt.StreamlitSink(st.container()))

# Any callable works as a sink
def print_progress(event: wt.ProgressEvent) -> None:
    if event.done:
        print(f"done in {event.elapsed:.1f}s")
    elif event.total:
        print(f"{event.phase}: {event.n}/{event.total} {event.message}")
    else:
        print(f"{event.phase} ...")

cmd('sample.h5', wsi_path='sample.ndpi', on_progress=print_progress)

# Process-wide default for commands called without on_progress= (a name, a callable, or None)
wt.set_default_progress('rich')
wt.set_default_progress(print_progress)
wt.set_default_progress(None)   # silent

Sinks are stateful (they hold the current bar), so create a new one per command call; resolve_sink and the defaults machinery already do that for you.

Cancellation

Pass should_cancel, a zero-argument callable returning True when the command should stop. It is polled at every phase boundary and, in the long phases, after every batch / row strip / tile / patch. When it returns True the command raises wt.Cancelled after cleaning up its partial output (an interrupted extract leaves no half-written features dataset behind, an interrupted cache removes the partial cache).

import threading
import wsi_toolbox as wt

stop = threading.Event()          # set from another thread, a signal handler, a UI button, ...

cmd = wt.FeatureExtractionCommand(model='uni2', preset='uni2', device='cuda:0')
try:
    cmd('sample.h5', wsi_path='sample.ndpi', should_cancel=stop.is_set)
except wt.Cancelled as e:
    print('cancelled:', e)         # "cancelled during 'Processing patches'"

should_cancel works with on_progress=None too; the two are independent.

Custom models (TilePreset)

A built-in preset is just a wt.TilePreset looked up by name (wt.get_tile_preset('uni2')). To run your own encoder, build one yourself and pass it wherever a preset name is accepted — preset= of FeatureExtractionCommand, or wt.set_default_preset(...):

import wsi_toolbox as wt

def create_my_model():
    # Return a fresh torch.nn.Module. Do NOT move it to a device or call .eval(); the command does that.
    import timm
    return timm.create_model('hf-hub:MahmoodLab/uni', pretrained=True, dynamic_img_size=True, init_values=1e-5)

my_preset = wt.TilePreset(
    name='my-uni',                          # written to {model}/.attrs['preset'] in the HDF5 file
    create_model=create_my_model,
    norm_mean=(0.485, 0.456, 0.406),        # input normalization (RGB, 0-1 scale); ImageNet by default
    norm_std=(0.229, 0.224, 0.225),
    extract_fn=None,                        # None: model.forward_features(x)[:, 0] (CLS token)
)

cmd = wt.FeatureExtractionCommand(model='my-uni', preset=my_preset, device='cuda:0')
cmd('sample.h5', wsi_path='sample.ndpi')

With extract_fn=None the module must expose forward_features(x) returning (B, 1 + tokens, dim) with the CLS token first, and patch_embed.proj.kernel_size (used for with_latent=True). For any other interface give extract_fn=lambda model, x: ... returning the (B, dim) features; latent extraction is then skipped.

Phase names

Sinks receive the phase names below, in this order, for each command. A phase with a known total advances step by step; the others emit a single event at n=0. ClusterWithUmapCommand runs UMAP then clustering through one reporter, so its stream is the two lists back to back, ending in a single done event.

Command Phases (total)
FeatureExtractionCommand Initializing model → Processing patches (batches; message = reader stats) → Writing
CacheCommand Caching patches (row strips; message = reader stats)
AggregateCommand Loading features → Initializing model → Aggregating → Writing
ClusteringCommand Loading features → PCA → KNN → Building graph → Leiden clustering → Finalizing → Sorting clusters (only with sort_clusters=True) → Writing
UmapCommand Loading features → UMAP → Writing
PCACommand Loading features → PCA → Writing
ClusterWithUmapCommand UmapCommand phases, then ClusteringCommand phases
Preview*Command Rendering patches (patches)
DziCommand Generating tiles (tiles; message = Level L: row r/R)
PyramidCommand Building pyramid (n = percent done, total = 100)
ShowCommand none (no on_progress)

When a command skips its work (output already present and overwrite=False) no phase is emitted, but the final done=True event still arrives.

CLI progress

The CLI uses the same machinery. --progress selects the sink for every subcommand:

wt extract -i sample.ndpi --progress rich    # default
wt extract -i sample.ndpi --progress tqdm
wt extract -i sample.ndpi --progress none    # no bars (logs only)

Commands

CLI is available as wsi-toolbox or wt. Each command has --help. Options shared by all subcommands: --preset, -M/--model, -D/--device, --progress rich|tqdm|none, --seed, -v.


extract

Extract patch embeddings from WSI using foundation models.

CLI Python
wt extract -i sample.ndpi -o sample.h5 FeatureExtractionCommand(model='uni2', preset='uni2')(h5_path, wsi_path=...)
wt extract -i sample.ndpi -o sample.h5
wt extract -i sample.ndpi --preset gigapath        # Use Gigapath
wt extract -i sample.ndpi --preset gigapath-flash  # GigaPath-Flash (ViT-S, ~50x cheaper)
wt extract -i sample.ndpi --preset virchow2        # Use Virchow2
wt extract -i sample.ndpi --preset conch15_768     # CONCH v1.5 (768D, TITAN-ready)
wt extract -i sample.ndpi --preset midnight        # OpenMidnight
wt extract -i sample.ndpi -L                       # Include latent features
wt extract -i sample.ndpi -D cuda:0,1              # Multi-GPU parallel
cmd = wt.FeatureExtractionCommand(model='uni2', preset='uni2', batch_size=256, with_latent=True)
result = cmd('sample.h5', wsi_path='sample.ndpi')
# result.feature_dim, result.patch_count

aggregate

Run a slide-level aggregator (e.g. TITAN) on tile features to produce a single slide embedding.

CLI Python
wt aggregate -i sample.h5 AggregateCommand(slide_preset='titan', tile_model='conch15_768')('sample.h5')
# Auto-resolve: scans the h5 for a tile preset compatible with titan (= conch15_768)
wt aggregate -i sample.h5

# Explicit storage key (multiple compatible groups → required)
wt aggregate -i sample.h5 -M conch15_768
cmd = wt.AggregateCommand(slide_preset='titan', tile_model='conch15_768')
result = cmd('sample.h5')
# → conch15_768/aggregates/titan/feature  (D=768)

Requires conch15_768/features to exist (run wt extract --preset conch15_768 -S 512 first).


cluster

Run Leiden clustering on embeddings.

CLI Python
wt cluster -i sample.h5 ClusteringCommand(model='uni2')(['sample.h5'])
wt cluster -i sample.h5
wt cluster -i sample.h5 --resolution 0.5   # Fewer clusters
cmd = wt.ClusteringCommand(model='uni2', resolution=1.0)
result = cmd(['sample.h5'])
# result.cluster_count, result.target_path

See Advanced Usage for multi-file clustering and sub-clustering.


preview

Generate cluster overlay image. Requires WSI with same stem.

CLI Python
wt preview -i sample.h5 PreviewClustersCommand(model='uni2')('sample.h5')
wt preview -i sample.h5
wt preview -i sample.h5 -f 1 2 3           # Filter to clusters 1,2,3
wt preview -i sample.h5 --size 32          # Smaller thumbnails
cmd = wt.PreviewClustersCommand(model='uni2', size=64)
img = cmd('sample.h5', namespace='default')
img.save('preview.jpg')

umap

Compute UMAP projection.

CLI Python
wt umap -i sample.h5 UmapCommand(model='uni2')(['sample.h5'])
wt umap -i sample.h5
wt umap -i sample.h5 --show                # Display plot
wt umap -i sample.h5 --save                # Save plot
cmd = wt.UmapCommand(model='uni2', n_neighbors=15, min_dist=0.1)
result = cmd(['sample.h5'])
# result.target_path → 'uni2/default/umap'

pca

Compute PCA projection.

CLI Python
wt pca -i sample.h5 PCACommand(model='uni2')(['sample.h5'])
wt pca -i sample.h5
wt pca -i sample.h5 -n 2                   # 2 components
wt pca -i sample.h5 --show                 # Display plot
cmd = wt.PCACommand(model='uni2', n_components=1, scaler='minmax')
result = cmd(['sample.h5'])
# result.target_path → 'uni2/default/pca1'

preview-score

Generate score heatmap overlay. Requires WSI with same stem.

CLI Python
wt preview-score -i sample.h5 -n pca1 PreviewScoresCommand(model='uni2')('sample.h5', score_name='pca1')
wt preview-score -i sample.h5 -n pca1
wt preview-score -i sample.h5 -n pca1 --cmap viridis
wt preview-score -i sample.h5 -n pca1 --invert
cmd = wt.PreviewScoresCommand(model='uni2', size=64)
img = cmd('sample.h5', score_name='pca1', cmap_name='jet')
img.save('pca_heatmap.jpg')

show

Display HDF5 file structure.

CLI Python
wt show -i sample.h5 ShowCommand()('sample.h5')
wt show -i sample.h5
wt show -i sample.h5 -v                    # Verbose

thumb

Generate thumbnail from WSI.

CLI Python
wt thumb -i sample.ndpi wsi.generate_thumbnail()
wt thumb -i sample.ndpi
wt thumb -i sample.ndpi -w 1024            # Specify width

dzi

Export WSI to Deep Zoom Image format (for OpenSeadragon).

CLI Python
wt dzi -i sample.ndpi -o ./out DziCommand()(wsi_path, output_dir, name)
wt dzi -i sample.ndpi -o ./output
wt dzi -i sample.ndpi -o ./output -t 512   # Tile size

To serve DZI tiles on demand instead of writing them all, use wsi_toolbox.dzi (see below).


pyramid

Convert a WSI into a tiled pyramidal TIFF optimised for DZI serving: every 2x level present, 512 px JPEG (Q85) tiles, BigTIFF. A 256 px DZI tile is then one contiguous read, which is much faster than reading an NDPI / SVS original, above all on HDD / NFS. Needs the libvips CLI (vips, with the openslide loader) on PATH; it runs as a subprocess, so no Python dependency is added.

CLI Python
wt pyramid -i sample.ndpi PyramidCommand()(wsi_path, output_path)
wt pyramid -i sample.ndpi                          # -> sample.pyramid.tif
wt pyramid -i sample.ndpi -o out.tif -q 90 -t 256  # JPEG quality / tile size

The output is written to .<name>.tmp and renamed into place, so a failed or cancelled run leaves nothing behind. create_wsi_file("sample.pyramid.tif") opens it like any pyramidal TIFF.

Benchmark against the originals (NDPI / SVS / TIFF / MIRAX on SSD / HDD / NFS) and how to rerun it on your slides: _docs/benchmark-pyramid-dzi.md (scripts/bench_pyramid.py).

DZI serving (wsi_toolbox.dzi)

DziGenerator answers .dzi and tile requests for any WSI toolbox opens (a pyramid.tif is fastest):

from wsi_toolbox import DziGenerator, DziTileNotFound, create_wsi_file, encode_tile

wsi = create_wsi_file("sample.pyramid.tif")      # one WSI object per thread
dzi = DziGenerator(wsi, tile_size=256, overlap=0)
xml = dzi.xml()                                  # {name}.dzi
try:
    tile = dzi.tile(level, col, row)             # RGB uint8, exactly the spec's tile size
except DziTileNotFound:
    ...                                          # 404
jpeg = encode_tile(tile, quality=90)             # {name}_files/{level}/{col}_{row}.jpeg

Tiles always have the size the Deep Zoom spec gives, even when native levels are rounded (SVS 4.0001, odd sizes). Levels that exist natively are copied without resampling; the others are resampled from the next finer level with Lanczos over the exact source box.


cache (optional)

Pre-cache patch images for repeated access:

wt cache -i sample.ndpi -o sample.h5
wt extract -i sample.h5   # Uses cache
wt preview -i sample.h5   # Uses cache

Structure:

cache/{patch_size}/
├── patches       # [N, H, W, 3] images
└── coordinates   # [N, 2] coords

migrate

Migrate old HDF5 format to new format.

wt migrate -i sample.h5
wt migrate -i sample1.h5 sample2.h5      # Multiple files

HDF5 File Structure

All data is stored in a single HDF5 file. Use wt show -i sample.h5 to inspect.

Root Attributes (Metadata)

with h5py.File('sample.h5', 'r') as f:
    # WSI metadata
    f.attrs['original_mpp']      # Original microns per pixel
    f.attrs['original_width']    # Original width (px)
    f.attrs['original_height']   # Original height (px)

    # Default extraction grid (legacy/back-compat; per-preset values live on {model}/.attrs)
    f.attrs['mpp']
    f.attrs['patch_count']
    f.attrs['cols']
    f.attrs['rows']

Tile features

Features are stored under {model}/. model (the storage key) defaults to the preset name (e.g. uni, conch15_768) but is a free string when -M is given.

{model}/                  attrs: preset, patch_size, target_mpp, mpp, cols, rows, patch_count
├── features                   # [N, D]
├── coordinates                # [N, 2] level-0 (x, y) in pixels
├── latent_features            # [N, L, D] optional (with -L flag)
├── aggregates/                # slide-level aggregator outputs
│   └── {slide_preset}/
│       └── feature            # [D_slide]
└── {namespace}/               # analysis results (see below)

Feature dim per tile preset: uni: 1024, uni2: 1536, gigapath: 1536, gigapath-flash: 384, virchow/2: 1280, h-optimus-0: 1536, conch15: 1024, conch15_768: 768, midnight: 1536, phikon2: 1024.

with h5py.File('sample.h5', 'r') as f:
    features = f['uni/features'][:]                              # (N, 1024)
    coords   = f['uni/coordinates'][:]                           # (N, 2)
    preset   = f['uni'].attrs['preset']                          # which foundation model
    slide    = f['conch15_768/aggregates/titan/feature'][:]      # (768,)

Analysis Results (Hierarchical)

Results are stored under {model}/{namespace}/.

{model}/{namespace}/
├── clusters     # [N] cluster labels (int)
├── umap         # [N, 2] UMAP coordinates
└── pca1         # [N] PCA scores

Namespace:

  • Single file: default
  • Multi-file: file1+file2+... (auto-generated)

Sub-clustering (filter hierarchy):

{model}/default/clusters                           # Base
{model}/default/filter/1+2+3/clusters              # Sub-cluster of 1,2,3
{model}/default/filter/1+2+3/filter/0+1/clusters   # Further nesting

See Advanced Usage for examples.

Writing Status

Large datasets have a writing attribute (True during write, False when complete).

if f['uni/features'].attrs.get('writing', False):
    raise RuntimeError('Dataset is incomplete')

Advanced Usage

Multi-file Joint Clustering

Cluster multiple WSIs together to find common patterns across samples.

# 1. Extract features from each WSI
wt extract -i sample1.ndpi -o sample1.h5
wt extract -i sample2.ndpi -o sample2.h5

# 2. Joint clustering (namespace auto-generated as "sample1+sample2")
wt cluster -i sample1.h5 sample2.h5

# 3. Analysis on joint clusters
wt pca -i sample1.h5 sample2.h5
wt umap -i sample1.h5 sample2.h5

# 4. Preview each file (uses shared cluster labels)
wt preview -i sample1.h5 -N sample1+sample2
wt preview -i sample2.h5 -N sample1+sample2
# Joint clustering
cmd = wt.ClusteringCommand(model='uni2')
result = cmd(['sample1.h5', 'sample2.h5'])
# → namespace: 'sample1+sample2'
# → uni2/sample1+sample2/clusters in both files

Sub-clustering

Analyze a subset of clusters in more detail.

# Sub-cluster within clusters 1,2,3
wt cluster -i sample1.h5 sample2.h5 -f 1 2 3

# PCA/UMAP on filtered subset
wt pca -i sample1.h5 sample2.h5 -f 1 2 3
wt umap -i sample1.h5 sample2.h5 -f 1 2 3

# Preview filtered clusters
wt preview -i sample1.h5 -N sample1+sample2 -f 1 2 3
# Sub-cluster
cmd = wt.ClusteringCommand(model='uni2', parent_filters=[[1, 2, 3]])
cmd(['sample1.h5', 'sample2.h5'])
# → uni2/sample1+sample2/filter/1+2+3/clusters

# PCA on filtered subset
cmd = wt.PCACommand(model='uni2', parent_filters=[[1, 2, 3]])
cmd(['sample1.h5', 'sample2.h5'])
# → uni2/sample1+sample2/filter/1+2+3/pca1

Streamlit App

uv run task app

# Environment variables
WT_PRESET=gigapath WT_DEVICE=cuda:1 WT_PREFETCH=2 uv run task app

Development

git clone https://github.com/technoplasm/wsi-toolbox.git
cd wsi-toolbox
uv sync

uv run wt --help
uv run task app
uv run task test     # pytest; CPU only, no model downloads
uv run task lint

License

MIT

Release files for wsi-toolbox 0.6.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for wsi-toolbox 0.6.1
File Size Uploaded
wsi_toolbox-0.6.1.tar.gz 296.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for wsi-toolbox 0.6.1
File Interpreter ABI Platform
wsi_toolbox-0.6.1-py3-none-any.whl Python 3 none any Details

Total release size: 429.0 kB

Release files / wsi_toolbox-0.6.1.tar.gz

Download URL wsi_toolbox-0.6.1.tar.gz
Size 296.6 kB
Tags Source
SHA-256 checksum
How to use checksums
4d09d9b2394356668d736751eadc96175bb73495093034216893940f59005f24
BLAKE2b-256 checksum
How to use checksums
05311f40abc5cb3ee55f105dfe21592fb5159f545513d89aefa9b42c70ed2542
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.7

Release files / wsi_toolbox-0.6.1-py3-none-any.whl

Download URL wsi_toolbox-0.6.1-py3-none-any.whl
Size 132.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
4ba585a1cab761bf0eba73ada59e16c1f6da01dcd40d008496f22e20b7ae909d
BLAKE2b-256 checksum
How to use checksums
3eef072837264a04de08fb94ddb2af71479a3cf2ac5b7c2ae0ccec01a4e6e13f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.7

Release history Release notifications | RSS feed

This release

0.6.1 This release

2 release files

0.6.0

2 release files

0.5.1

2 release files

0.5.0

2 release files

0.4.2

2 release files

0.4.1

2 release files

0.4.0

2 release files

0.3.3

2 release files

0.3.2

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page