Skip to main content

High-performance Macenko PCA stain deconvolution powered by Rust

Project description

Macenko PCA

Build Tests Lint PyPI - Version PyPI - Python Version


High-performance stain matrix estimation and colour deconvolution for histology images using the Macenko PCA method, with the compute-intensive core written in Rust via PyO3.

Supported platforms: Linux and macOS only. Windows is not supported due to differences in BLAS/LAPACK / OpenBLAS builds that can cause numerical and linking inconsistencies; to ensure reproducible numerical results we build and test only on Linux and macOS.

This implements the method described in:

Macenko, M. et al. "A method for normalizing histology slides for quantitative analysis." ISBI 2009.

And was based off the implementation from HistomicsTK

https://github.com/DigitalSlideArchive/HistomicsTK

Performance improvements between the HistomicsTK implementation and this one are discussed here. We saw almost a quarter of the RAM usage and up to a 7x speedup in real world examples.

Features

Feature Detail
Performance Core SVD, PCA projection, and angle binning run in compiled Rust with Rayon parallelism
Simple API Six functions cover the full workflow — estimate stain vectors, decompose, and reconstruct
NumPy native Accepts and returns standard NumPy arrays — no special data structures required
Precision-aware Pass float32 arrays to halve RAM usage — the dtype of your input controls which Rust code path runs
Platform support Built with maturin for Linux and macOS (x86_64 + arm64). Windows is not supported.

Quick Start

Installation

pip install macenko-pca

Full Workflow Example

import numpy as np
from macenko_pca import (
    rgb_separate_stains_macenko_pca,
    rgb_color_deconvolution,
    reconstruct_rgb,
)

# Load or create an RGB image (H×W×3, values in [0, 255])
im_rgb = np.random.rand(256, 256, 3) * 255.0

# 1. Estimate the 3×3 stain matrix from the image
stain_matrix = rgb_separate_stains_macenko_pca(im_rgb)
print("Stain matrix:\n", stain_matrix)

# 2. Decompose the image into per-stain concentration channels
concentrations = rgb_color_deconvolution(im_rgb, stain_matrix)

hematoxylin = concentrations[:, :, 0]
eosin = concentrations[:, :, 1]
residual = concentrations[:, :, 2]

# 3. Modify concentrations (e.g. isolate hematoxylin only)
concentrations_h_only = concentrations.copy()
concentrations_h_only[:, :, 1] = 0.0  # zero-out eosin
concentrations_h_only[:, :, 2] = 0.0  # zero-out residual

# 4. Reconstruct back to RGB
im_hematoxylin_only = reconstruct_rgb(concentrations_h_only, stain_matrix)

Step-by-Step API

from macenko_pca import (
    rgb_to_sda,
    separate_stains_macenko_pca,
    color_deconvolution,
    reconstruct_rgb,
)

# Convert RGB to SDA (Stain Density Absorbance) space
im_sda = rgb_to_sda(im_rgb)

# Estimate stain vectors from the SDA image
stain_matrix = separate_stains_macenko_pca(im_sda)

# Decompose SDA image into stain concentrations
concentrations = color_deconvolution(im_sda, stain_matrix)

# Reconstruct RGB from (possibly modified) concentrations
im_reconstructed = reconstruct_rgb(concentrations, stain_matrix)

Half the RAM — Use float32

# Simply cast your input to float32 — the Rust backend will use f32 throughout
im_rgb_f32 = im_rgb.astype(np.float32)
stain_matrix = rgb_separate_stains_macenko_pca(im_rgb_f32)       # f32
concentrations = rgb_color_deconvolution(im_rgb_f32, stain_matrix)  # f32
reconstructed = reconstruct_rgb(concentrations, stain_matrix)        # f32

Precision / dtype rules

The dtype of your input array controls which Rust code path is taken:

Input dtype Computation dtype Notes
float64 f64 Full precision (default for plain Python floats)
float32 f32 ≈ half the RAM — recommended when full precision is unnecessary
float16 f32 Promoted to f32 (no f16 LAPACK exists)
integer types f64 Promoted to f64 for backward compatibility

The return array's dtype always matches the computation dtype.

API Reference

Stain Matrix Estimation

rgb_separate_stains_macenko_pca(im_rgb, ...)

End-to-end: takes an RGB image (H, W, 3) and returns a (3, 3) stain matrix.

separate_stains_macenko_pca(im_sda, ...)

Lower-level: operates on an image already in SDA space.

Colour Conversion

rgb_to_sda(im_rgb, ...)

Converts an RGB image or matrix to SDA (stain-density-absorbance) space.

Colour Deconvolution (Applying Stain Vectors)

color_deconvolution(im_sda, stain_matrix)

Decomposes an SDA image into per-stain concentration channels using the inverse of the stain matrix. Each output channel i holds the concentration of stain i.

rgb_color_deconvolution(im_rgb, stain_matrix, bg_int=None)

Convenience wrapper that converts RGB → SDA → concentrations in a single call.

Reconstruction

reconstruct_rgb(concentrations, stain_matrix, bg_int=None)

Reconstructs an RGB image from stain concentrations and a stain matrix. Inverts the deconvolution: SDA = concentrations × Wᵀ, then converts SDA back to RGB. Useful for stain normalisation workflows where you modify concentration channels and then reconstruct.

See the full API documentation for parameter details.

Development Setup

Prerequisites: Python 3.9+, a Rust toolchain, and Hatch.

On Linux you also need OpenBLAS development headers (libopenblas-dev on Debian/Ubuntu). On macOS: brew install openblas pkg-config.

git clone https://github.com/LavLabInfrastructure/macenko-pca.git
cd macenko-pca
pip install maturin hatch
maturin develop --release

Optionally install pre-commit hooks:

pip install pre-commit
pre-commit install

Common Commands

Run these directly or use the provided Makefile shortcuts (e.g. make test, make lint).

Task Command
Build Rust extension in-place maturin develop --release
Run tests make test
Tests + coverage make cov
Lint Python hatch run lint:check
Format Python hatch run lint:format
Auto-fix lint hatch run lint:fix
Format + fix + lint hatch run lint:all
Type check hatch run types:check
Build docs hatch run docs:build-docs
Serve docs hatch run docs:serve-docs
Build wheel maturin build --release
Clean artifacts make clean
Rust lints make cargo-clippy
Rust tests make cargo-test

Docker

# Run tests via Docker
docker build --target maturin -t macenko-pca:maturin .
docker run --rm -e HATCH_ENV=test macenko-pca:maturin cov

# Production image (just the installed wheel)
docker build --target prod -t macenko-pca:prod .

Publishing to PyPI

This project uses trusted publishing (OIDC) — no API tokens or secrets are needed. The publish.yml workflow handles everything automatically.

One-Time Setup (PyPI)

  1. Go to https://pypi.org/manage/account/publishing/ (logged in as a maintainer of the lavlab org).
  2. Click "Add a new pending publisher" and fill in:
    • PyPI project name: macenko-pca
    • Owner: lavlab (the GitHub organisation)
    • Repository: macenko-pca
    • Workflow name: publish.yml
    • Environment name: pypi
  3. (Optional) Repeat on TestPyPI with environment name testpypi to enable dry-run publishes.

One-Time Setup (GitHub)

  1. In the repository settings, go to Environments.
  2. Create an environment called pypi.
    • Optionally add a protection rule requiring manual approval before publishing.
  3. (Optional) Create an environment called testpypi for test publishes.

How to Release

# 1. Bump the version in src/macenko_pca/__about__.py and Cargo.toml
# 2. Commit and tag
git add -A
git commit -m "release: v0.2.0"
git tag v0.2.0
git push && git push --tags

# 3. Create a GitHub Release from the tag (via the web UI or `gh` CLI)
gh release create v0.2.0 --generate-notes

Creating the release triggers publish.yml, which:

  1. Builds wheels on Linux (manylinux) and macOS (x86_64 + arm64). Windows builds are intentionally omitted due to platform LAPACK/BLAS inconsistencies that affect numerical reproducibility.
  2. Builds a source distribution.
  3. Publishes everything to PyPI via trusted publishing.

Testing a Publish (Without a Release)

You can manually trigger the workflow against TestPyPI:

  1. Go to Actions → Publish to PyPI → Run workflow.
  2. Select testpypi as the target.
  3. Verify the package at https://test.pypi.org/project/macenko-pca/.

Project Structure

macenko-pca/
├── src/
│   └── macenko_pca/            # Python package source
│       ├── __init__.py          # Public API & version export
│       ├── __about__.py         # Version string
│       ├── deconvolution.py     # Pythonic wrappers with dtype dispatch
│       └── py.typed             # PEP 561 marker
├── rust/                        # Rust source (compiled via PyO3 + maturin)
│   ├── lib.rs                   # PyO3 module entry point (f32 + f64 variants)
│   ├── float_trait.rs           # MacenkoFloat supertrait (f32/f64)
│   ├── color_conversion.rs      # RGB → SDA transform (generic)
│   ├── color_deconvolution.rs   # SDA → concentrations, RGB reconstruction
│   ├── complement_stain_matrix.rs
│   ├── linalg.rs                # SVD, magnitude, normalisation (generic)
│   ├── rgb_separate_stains_macenko_pca.rs
│   ├── separate_stains_macenko_pca.rs
│   └── utils.rs                 # Image ↔ matrix helpers
├── tests/
│   ├── conftest.py              # Shared pytest fixtures
│   └── test_deconvolution.py    # Library function tests (104 tests)
├── docs/                        # MkDocs source files
├── .github/
│   ├── workflows/
│   │   ├── build.yml            # CI: build wheels on every push/PR
│   │   ├── pytest.yml           # CI: run tests across Python versions
│   │   ├── lint.yml             # CI: ruff lint + format check
│   │   └── publish.yml          # CD: publish to PyPI on release
│   └── dependabot.yml           # Auto-update deps, actions, Docker, Cargo
├── Cargo.toml                   # Rust crate configuration
├── pyproject.toml               # Python project & tool config (maturin backend)
├── Dockerfile                   # Multi-stage build (maturin / dev / prod)
├── Makefile                     # Dev shortcuts
├── mkdocs.yml                   # Docs config
├── .pre-commit-config.yaml      # Pre-commit hooks
├── .editorconfig                # Editor consistency
└── .gitignore

Design Philosophy

This project follows a library-first approach:

  1. All logic lives in importable modules under src/macenko_pca/.
  2. Heavy computation is delegated to Rust (rust/) for maximum throughput — SVD via ndarray-linalg, parallelism via rayon. All Rust functions are generic over f32/f64 via the MacenkoFloat trait.
  3. The Python layer (deconvolution.py) detects the input array's dtype and dispatches to the appropriate typed Rust function, providing input validation and rich docstrings.
  4. Tests call library functions directly.

This keeps your code reusable whether it's called from another package, a Jupyter notebook, or a web API.

Contributing

See CONTRIBUTING.md for development guidelines.

License

macenko-pca is distributed under the terms of the MIT license.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

macenko_pca-1.0.4.tar.gz (58.1 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

macenko_pca-1.0.4-cp314-cp314-macosx_11_0_arm64.whl (450.0 kB view details)

Uploaded CPython 3.14macOS 11.0+ ARM64

macenko_pca-1.0.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (11.1 MB view details)

Uploaded CPython 3.13manylinux: glibc 2.17+ x86-64

macenko_pca-1.0.4-cp313-cp313-macosx_11_0_arm64.whl (450.4 kB view details)

Uploaded CPython 3.13macOS 11.0+ ARM64

macenko_pca-1.0.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (11.1 MB view details)

Uploaded CPython 3.12manylinux: glibc 2.17+ x86-64

macenko_pca-1.0.4-cp312-cp312-macosx_11_0_arm64.whl (450.6 kB view details)

Uploaded CPython 3.12macOS 11.0+ ARM64

macenko_pca-1.0.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (11.1 MB view details)

Uploaded CPython 3.11manylinux: glibc 2.17+ x86-64

macenko_pca-1.0.4-cp311-cp311-macosx_11_0_arm64.whl (450.3 kB view details)

Uploaded CPython 3.11macOS 11.0+ ARM64

macenko_pca-1.0.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (11.1 MB view details)

Uploaded CPython 3.10manylinux: glibc 2.17+ x86-64

macenko_pca-1.0.4-cp310-cp310-macosx_11_0_arm64.whl (450.6 kB view details)

Uploaded CPython 3.10macOS 11.0+ ARM64

macenko_pca-1.0.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (11.1 MB view details)

Uploaded CPython 3.9manylinux: glibc 2.17+ x86-64

macenko_pca-1.0.4-cp39-cp39-macosx_11_0_arm64.whl (452.7 kB view details)

Uploaded CPython 3.9macOS 11.0+ ARM64

File details

Details for the file macenko_pca-1.0.4.tar.gz.

File metadata

  • Download URL: macenko_pca-1.0.4.tar.gz
  • Upload date:
  • Size: 58.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for macenko_pca-1.0.4.tar.gz
Algorithm Hash digest
SHA256 22bcd38a69fdf341ac6b8545b94cabf6cca7f72ef838274234b200f224dd34ca
MD5 aee37e1eff80b0f9227f369b0005ff2d
BLAKE2b-256 8da5b6ada055ed1ab7bca1159507a5a6e40c109a686a499705121d331f5923e0

See more details on using hashes here.

Provenance

The following attestation bundles were made for macenko_pca-1.0.4.tar.gz:

Publisher: publish.yml on laviolette-lab/macenko-pca

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file macenko_pca-1.0.4-cp314-cp314-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for macenko_pca-1.0.4-cp314-cp314-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 fe4d2be3e630a8675a403509095c28ca0decce91cfd173e215e68b3ca4a15bcc
MD5 9adb83d2f65b948d7e47bddc49528afa
BLAKE2b-256 34368ca1056ee8530a4205462c32334aadd7e739674d171a447b96d31232f81f

See more details on using hashes here.

Provenance

The following attestation bundles were made for macenko_pca-1.0.4-cp314-cp314-macosx_11_0_arm64.whl:

Publisher: publish.yml on laviolette-lab/macenko-pca

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file macenko_pca-1.0.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for macenko_pca-1.0.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 c0384f4704f77a25f476a37101655d6eda01ef3fedad85c42b208321ec01c613
MD5 a72acb7e3456768316066e527118e6d8
BLAKE2b-256 84b08ff9c1240d1906024b287b514d06fd86e5e775300618fff42f2c74d1dba2

See more details on using hashes here.

Provenance

The following attestation bundles were made for macenko_pca-1.0.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: publish.yml on laviolette-lab/macenko-pca

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file macenko_pca-1.0.4-cp313-cp313-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for macenko_pca-1.0.4-cp313-cp313-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 7d3b30aab3b17362d629a4c719d98fdcfa808ecab8580472f49772a36d0887b9
MD5 4317ea003c1429d09216a156ce1bef6a
BLAKE2b-256 00035f0b1d8ae83241983b4c84bddc144af8a6b5d89969e26906fda1b54fba62

See more details on using hashes here.

Provenance

The following attestation bundles were made for macenko_pca-1.0.4-cp313-cp313-macosx_11_0_arm64.whl:

Publisher: publish.yml on laviolette-lab/macenko-pca

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file macenko_pca-1.0.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for macenko_pca-1.0.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 43474209e3bb4ebeb9c39c3aa7fff629c36163e7fb44281a918bedcb6be99956
MD5 3f519eae60909a6d380273d3c1be24b7
BLAKE2b-256 b722e8b5053d05bef5512a0c1ce2846d826a29c6246582b2342a749b9dcb6916

See more details on using hashes here.

Provenance

The following attestation bundles were made for macenko_pca-1.0.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: publish.yml on laviolette-lab/macenko-pca

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file macenko_pca-1.0.4-cp312-cp312-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for macenko_pca-1.0.4-cp312-cp312-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 d5ca334456e8e6d0899ad00922f7c8a62443d03b4c636d8f79fc3be72fc1e5b3
MD5 c070b289b660f4e9a2ce4d9d366833a0
BLAKE2b-256 5076abd13d04508e9dbb5a260c330411a146442cb04b0e6e6fdd87c459627b43

See more details on using hashes here.

Provenance

The following attestation bundles were made for macenko_pca-1.0.4-cp312-cp312-macosx_11_0_arm64.whl:

Publisher: publish.yml on laviolette-lab/macenko-pca

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file macenko_pca-1.0.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for macenko_pca-1.0.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 21b19a8ea60dbc99c5a57c30a0fc7ed8b5862a28eaf865705046fa8e2e8479b5
MD5 12a871334648eefb55f31644b22be3a5
BLAKE2b-256 1db42c86039de136efda733dddd08db0392f47c1ef7dd8dbbbc2d51ca9212788

See more details on using hashes here.

Provenance

The following attestation bundles were made for macenko_pca-1.0.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: publish.yml on laviolette-lab/macenko-pca

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file macenko_pca-1.0.4-cp311-cp311-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for macenko_pca-1.0.4-cp311-cp311-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 d11472e936ffe59fb91ac1f665d0a82cef29e5d8d3d7b9cf539ae1e7e9bca9d3
MD5 3d23411d5e18551354f340b821a87321
BLAKE2b-256 4d8dbf6e50580da812e51edec22faff3f609396104f80a4a5864c4c3c69ad7a4

See more details on using hashes here.

Provenance

The following attestation bundles were made for macenko_pca-1.0.4-cp311-cp311-macosx_11_0_arm64.whl:

Publisher: publish.yml on laviolette-lab/macenko-pca

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file macenko_pca-1.0.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for macenko_pca-1.0.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 687ec495ced96afa3e6e63edcc20c48c1066dfb6f0ef31d58c7b0e5c18dab552
MD5 7d013dd29f486a5fe95254983c0239ee
BLAKE2b-256 69f90824107ccab48a173ef15d15e2158f947d2f4a93a47b1a22e0a0cbfa5d18

See more details on using hashes here.

Provenance

The following attestation bundles were made for macenko_pca-1.0.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: publish.yml on laviolette-lab/macenko-pca

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file macenko_pca-1.0.4-cp310-cp310-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for macenko_pca-1.0.4-cp310-cp310-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 822a66559d2be1c03ebde4cc02fb2f767be1a6ac2381407fe453cdd76c035f7c
MD5 574928488c6ca67a286e9042188f07ac
BLAKE2b-256 d12fc61d7865894f75f3dc3812aea56dff146424a96f3b50b464d67a2b9d6833

See more details on using hashes here.

Provenance

The following attestation bundles were made for macenko_pca-1.0.4-cp310-cp310-macosx_11_0_arm64.whl:

Publisher: publish.yml on laviolette-lab/macenko-pca

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file macenko_pca-1.0.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for macenko_pca-1.0.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 759a34d43b214305e75a1fb50a21ee4a69f5f5eb931493e966be2b6b588628b1
MD5 b431cde3ff6684abf8d542f9f86810ec
BLAKE2b-256 b67a7102c9f4c5ca98c79296a35bb7c5c7b0bfb6a93887bb1ed4808a5d7d177a

See more details on using hashes here.

Provenance

The following attestation bundles were made for macenko_pca-1.0.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: publish.yml on laviolette-lab/macenko-pca

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file macenko_pca-1.0.4-cp39-cp39-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for macenko_pca-1.0.4-cp39-cp39-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 dbbb8fd56f61fa98f103c0fd58024f6d82765443cba2717028c5baf7f6e520ef
MD5 a6aa670d30856b7aeb1397d2c2d61c8c
BLAKE2b-256 d03db8246a70026c7a2e0d6c966df620e7959d36f5a5d928712bf60a9513bc88

See more details on using hashes here.

Provenance

The following attestation bundles were made for macenko_pca-1.0.4-cp39-cp39-macosx_11_0_arm64.whl:

Publisher: publish.yml on laviolette-lab/macenko-pca

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page