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

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.0

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.0
File Size Uploaded
wsi_toolbox-0.6.0.tar.gz 282.7 kB Details

Built distribution (wheel)

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

Total release size: 409.6 kB

Release files / wsi_toolbox-0.6.0.tar.gz

Download URL wsi_toolbox-0.6.0.tar.gz
Size 282.7 kB
Tags Source
SHA-256 checksum
How to use checksums
92441815352e218b715fb0958186c3b2b1f7a0c49fe5ba68711d9434c087dce4
BLAKE2b-256 checksum
How to use checksums
4b6d72ca0ee6b214f0cae6a220599fb27c52abab49803d90427f952f56a519ca
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.0-py3-none-any.whl

Download URL wsi_toolbox-0.6.0-py3-none-any.whl
Size 126.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
070a98706c378c211cdca164ead2f59181a30fc0b58a1e0984d63a1c7e0b7302
BLAKE2b-256 checksum
How to use checksums
fa31deff54b4a1d1f366e05563b378f5b1a711b77dbc04ea0c1d1c0eb917432d
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.0 This release

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