tissuearea
Estimate the physical tissue area (mm²) on a whole slide image (WSI) by combining a tissue segmentation mask with the slide's microns-per-pixel (MPP) metadata.
tissuearea is a small, self-contained library: give it a slide (or a
thumbnail + dimensions + MPP) and it returns both the whole-slide tissue
area and the largest single section area, plus the full list of per-section
(connected-component) areas. It has no machine-learning dependencies — just
OpenSlide, OpenCV, scikit-image, and NumPy.
The segmentation primitives and area math are extracted, dependency-free, from the EasyMSI preprocessing pipeline; a default-config mask reproduces the EasyMSI production segmentation exactly.
How it works
A scale-s thumbnail maps linearly onto level 0, so each mask pixel covers
(W·mpp_x / mask_w) · (H·mpp_y / mask_h) µm², where W, H are the level-0 slide
dimensions and mask_w, mask_h the thumbnail dimensions.
- Segment the thumbnail: CLAHE + Otsu → pen/gray color filters →
remove-small-objects → fill-holes → optional dilation
(
tissuearea.build_tissue_mask). - Convert the boolean mask to area, reporting candidates
(
tissuearea.area_from_mask):whole_mm2— all detected tissuelargest_cc_mm2— the single largest connected component (one section)top2_sum_mm2,n_sections, and the sortedsection_areas_mm2
Connected components use 8-connectivity. MPP resolution order:
openslide.mpp-x/-y → an explicit fallback → 10 / objective-power.
Install
pip install tissuearea
That's the whole thing — the native OpenSlide library comes with it
(openslide-bin), so there's nothing to install separately. Requires Python ≥
3.11 (the floor comes from scikit-image 0.26).
If pip install can't find an OpenSlide wheel for your platform
openslide-bin ships wheels for Linux x86-64/aarch64 (glibc ≥ 2.28), macOS 11+,
and Windows x86-64. On older Linux (RHEL/CentOS 7) or Windows ARM there is no
wheel, pip will try to build OpenSlide from source, and it will likely fail. Use
conda there:
conda create -n tissuearea python=3.12 -y
conda activate tissuearea
conda install -c conda-forge openslide -y # native library
pip install --no-deps tissuearea # skip the openslide-bin wheel
pip install numpy opencv-python-headless openslide-python scikit-image pillow tqdm
Or use a system/module OpenSlide the same way.
From source
git clone https://github.com/xbh0403/tissuearea.git
cd tissuearea
pip install -e . # add ".[dev]" for the test suite
Or a one-command conda environment: conda env create -f environment.yml && conda activate tissuearea. Installing straight from git also works:
pip install "git+https://github.com/xbh0403/tissuearea.git". The repo is public,
so HTTPS needs no GitHub account or keys; contributors who push can use the SSH
URL git@github.com:xbh0403/tissuearea.git.
Usage
Command line
# input can be a single slide OR a folder (searched recursively);
# -i is optional — the input can be the first positional argument.
tissuearea slides/ # -> ./tissuearea_output/
tissuearea -i slide.svs -o results/
tissuearea -i slides/ -o results/ -t ffpe # FFPE cohort (gray filter on)
tissuearea -i slides/ -o results/ --jobs 8 # process 8 slides in parallel
tissuearea -i slides/ -o results/ --resume # continue an interrupted run
Folders are searched recursively (typical archive/{case}/{slide}.svs
layouts just work; pass --no-recursive to stay top-level). On start the
resolved configuration is printed and saved to <output>/run_config.txt, then a
progress bar tracks the batch. Everything lands in -o (default
./tissuearea_output):
| flag | default | meaning |
|---|---|---|
-i, --input (or positional) |
(required) | a slide file or a folder of slides |
-o, --output |
./tissuearea_output |
output dir (area.csv, area.json, thumbnails/, run_config.txt) |
-t, --type |
ff |
tissue prep: ff fresh-frozen (gray filter off) or ffpe (on) |
-j, --jobs N |
8 |
process N slides in parallel (use 1 for serial) |
--resume |
off | skip slides already in area.csv (continue a run) |
--no-recursive |
off | don't search subfolders |
--skip-png |
off | don't save labelled thumbnails (saved by default) |
--mpp MPP |
— | fallback microns/pixel for slides missing MPP metadata |
--mode |
largest_cc |
which area becomes the headline tissue_area_mm2 |
--scale |
32 |
thumbnail downsampling factor |
--label-min-area MM2 |
0 |
only label regions ≥ this size in thumbnails |
--no-json |
off | don't write area.json (written by default) |
--quiet |
off | minimal output (no banner / progress bar) |
Frozen tissue is often near-neutral, so the ff preset turns the gray filter off
(it would otherwise discard faint tissue, sometimes yielding an empty mask); FFPE
is well-stained, so ffpe keeps it on. Pick the one matching your cohort.
A crash-safe batch: area.csv is streamed row-by-row, so --resume picks up
where an interrupted run stopped, and both area.csv and area.json end up
holding the whole cohort — not just the slides the final run touched. Labelled
thumbnails keep the same filenames they would get in one uninterrupted run. Any
slide that fails is recorded in <output>/failures.csv and the process exits
non-zero (1 = some failed, 2 = none succeeded), so pipelines can tell.
area.csv — one row per slide:
| column | meaning |
|---|---|
tissue_area_mm2 |
the headline number (equals the --mode area; largest_cc by default) |
path |
absolute path to the source slide (traceable/joinable) |
whole_mm2 |
total tissue area (all regions) |
largest_cc_mm2 |
largest single region (one section) |
section_areas_mm2 |
every region's area, mm², largest-first, ;-separated |
top2_sum_mm2, n_sections, mask_fraction |
summary |
width, height, mpp_x, mpp_y, mask_scale |
slide metadata |
If you just want one number per slide, read tissue_area_mm2.
thumbnails/{slide_id}_regions.png — each region outlined and labelled with
its area (#1 = largest), plus a header (region count, total, largest).
--label-min-area suppresses text on tiny specks.
area.json (written by default; --no-json to skip) — full per-slide
records, each with a regions array that mirrors the thumbnail #N labels:
rank, area_mm2, centroid_xy, and bbox per section. So every area you see
drawn is saved and locatable.
Python
from tissuearea import tissue_area_for_slide, masking_config_for_type
# Pick the segmentation for your tissue type: "ff" (fresh-frozen) or "ffpe".
cfg = masking_config_for_type("ff")
out = tissue_area_for_slide("slide.svs", cfg, labeled_output_path="slide_regions.png")
print(out["largest_cc_mm2"], out["section_areas_mm2"]) # largest, and all regions
# Bare MaskingConfig() is the production default (gray filter ON = FFPE-style).
Work directly from a thumbnail you already have:
import numpy as np
from tissuearea import build_tissue_mask, area_from_mask, region_areas, draw_region_labels
mask = build_tissue_mask(thumbnail_rgb)
area = area_from_mask(mask, width=W, height=H, mpp_x=0.2628, mpp_y=0.2628)
regions = region_areas(mask, W, H, 0.2628, 0.2628) # [{rank, area_mm2, centroid_xy, ...}, ...]
draw_region_labels(thumbnail_rgb, mask, W, H, 0.2628, 0.2628, output_path="regions.png")
Public API
| Symbol | Purpose |
|---|---|
tissue_area_for_slide(path, config=None) |
Open a slide read-only and return all area candidates + metadata. |
tissue_area_from_thumbnail(thumb, W, H, mpp_x, mpp_y, config=None) |
Segment a thumbnail and compute areas. |
build_tissue_mask(thumb, config=None) |
RGB thumbnail → boolean tissue mask (= the production segmentation). |
area_from_mask(mask, W, H, mpp_x, mpp_y) |
Boolean mask → area dict (whole / largest_cc / top2 / sections). |
region_areas(mask, W, H, mpp_x, mpp_y) |
Per-region list (rank, area_mm2, centroid, bbox), largest-first. |
draw_region_labels(thumb, mask, W, H, mpp_x, mpp_y, output_path=None, min_area_mm2=0.0) |
Annotated thumbnail: each region outlined + area-labelled. |
resolve_mpp_xy(props, fallback_mpp=None) |
Resolve (mpp_x, mpp_y) from OpenSlide properties. |
visualize_mask(thumb, mask, output_path=None) |
Black out non-tissue for QA. |
SlideReader(path) |
Minimal read-only OpenSlide wrapper (dimensions, mpp, properties, get_thumbnail). |
MaskingConfig |
Segmentation parameters (the defaults are production; pass overrides). |
masking_config_for_type("ff"|"ffpe", **overrides) |
Optimal MaskingConfig for a tissue type (what the CLI --type uses). |
Choosing whole-slide vs. largest-section area
On a validation set of 307 fresh-frozen colorectal slides (reference = single-section areas), the best agreement came from disabling the gray filter and taking the largest connected component:
| Config (area mode) | Spearman | median |%err| | within ±25% |
|---|---|---|---|
filter_grays=False, largest_cc |
0.90 | 9.9% | 70.7% |
filter_grays=False, whole |
0.84 | 14.5% | — |
default, whole |
0.76 | 20.3% | 50.8% |
Two practical takeaways:
- Use
largest_cc_mm2when the reference measures one tissue section; a slide with two sections otherwise over-predicts ~2× onwhole_mm2. - Match the tissue type (
--type ff/masking_config_for_type("ff")) for faint, near-neutral fresh-frozen tissue — the gray filter otherwise discards it and can even yield empty masks. This is baked into theffpreset (the CLI default). FFPE (--type ffpe) keeps the filter on. Higher mask resolution (smallermask_scale) is not the lever; the gray filter is.
Testing
pytest
The area math is pinned against hand-computed values on synthetic masks; the
masking tests check the pipeline on a synthetic H&E-like thumbnail (blob
detected, background rejected, gray-filter toggle behaves monotonically). No
real .svs is required.
License
MIT
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file tissuearea-0.1.0.tar.gz.
File metadata
- Download URL: tissuearea-0.1.0.tar.gz
- Upload date:
- Size: 34.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
46b749a49116cd6da0222651ac722cf20a2e14e57f7e520850670b73266c9adc
|
|
| MD5 |
17de06f5ef68241e9ac813d5f241854d
|
|
| BLAKE2b-256 |
3e30a906473008a12c3ce6ab3071f13659b3bb3374f7f6a2f6959e945cffa2e3
|
Provenance
The following attestation bundles were made for tissuearea-0.1.0.tar.gz:
Publisher:
release.yml on xbh0403/tissuearea
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tissuearea-0.1.0.tar.gz -
Subject digest:
46b749a49116cd6da0222651ac722cf20a2e14e57f7e520850670b73266c9adc - Sigstore transparency entry: 2461079462
- Sigstore integration time:
-
Permalink:
xbh0403/tissuearea@174b5c3bd6fc16a9a648a7770e3b535ab270f16a -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/xbh0403
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@174b5c3bd6fc16a9a648a7770e3b535ab270f16a -
Trigger Event:
push
-
Statement type:
File details
Details for the file tissuearea-0.1.0-py3-none-any.whl.
File metadata
- Download URL: tissuearea-0.1.0-py3-none-any.whl
- Upload date:
- Size: 27.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0a67d2a05e8c8b9659084e8e6442b8d4bff9af52c452da62008b80f8e98f43ad
|
|
| MD5 |
eec116e280ebad73a67c3b7b19dd050e
|
|
| BLAKE2b-256 |
f1a0f8013e4083106a5f4b9d392aebfb7702b75c35e607b7f9060fd5aaba9eb9
|
Provenance
The following attestation bundles were made for tissuearea-0.1.0-py3-none-any.whl:
Publisher:
release.yml on xbh0403/tissuearea
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tissuearea-0.1.0-py3-none-any.whl -
Subject digest:
0a67d2a05e8c8b9659084e8e6442b8d4bff9af52c452da62008b80f8e98f43ad - Sigstore transparency entry: 2461079592
- Sigstore integration time:
-
Permalink:
xbh0403/tissuearea@174b5c3bd6fc16a9a648a7770e3b535ab270f16a -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/xbh0403
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@174b5c3bd6fc16a9a648a7770e3b535ab270f16a -
Trigger Event:
push
-
Statement type: