HE-to-IHC whole-slide image alignment pipeline
Project description
Whole-slide image alignment for H&E and multiplex IHC markers
"Align once. Map everywhere."
"一次配准,处处映射"
✨ Why HISAlign
| Problem | Solution |
|---|---|
| H&E and multiple IHC slides of the same tissue are spatially misaligned | Rigid + optical-flow non-rigid registration, per-marker alignment to H&E |
| Registration results depend on open slide handles and are hard to reuse | Only numpy arrays and transform parameters are saved; fully serializable |
| Downstream analysis needs to map ROIs from H&E to IHC | Offline warp_xy interface: provide level-0 coordinates and get mapped coordinates |
| Need a quick way to assess registration quality | Optional unified viz_report.html with slide summary and patch gallery |
🎨 Workflow
flowchart LR
HE["H&E Reference"]
IHC["IHC Markers\nCD3 / Ki67 / ..."]
PRE["Optical Density\nPreprocessing"]
RIGID["Rigid Registration"]
NR["Optical-Flow\nNon-Rigid"]
MODEL["model.pkl\nSerializable Model"]
WARP["warp_xy\nOffline Mapping"]
VIZ["viz_report.html"]
HE --> PRE
IHC --> PRE
PRE --> RIGID
RIGID --> NR
NR --> MODEL
MODEL --> WARP
NR --> VIZ
🚀 Installation
Install from PyPI using uv:
uv tool install hisalign
Or install it as a library in your current environment:
uv pip install hisalign
Verify the CLI entry point:
hisalign --help
🧪 Quickstart
Python API
from hisalign import HisAlign, HisAlignModel
# 1. Fit and save the model
aligner = HisAlign(
he_path="HE.kfb",
ihc_paths={"CD3": "CD3.svs", "Ki67": "Ki67.svs"},
registration_level=3,
max_image_dim_px=1024,
preprocessing="od",
feature_detector="kaze",
mpp=0.25, # explicit when slide metadata lacks MPP
)
model = aligner.fit()
model.save("model.pkl")
# 2. Offline coordinate mapping (no slides needed)
loaded = HisAlignModel.load("model.pkl")
mapped = loaded.warp_xy(
coords=[[1000, 2000]], # H&E level-0 pixel coordinates
marker="CD3",
direction="he_to_ihc",
)
print(mapped) # -> [[ihc_x, ihc_y]]
CLI
1. Register and save the model
hisalign register \
--he HE.kfb \
--ihc CD3=CD3.svs \
--ihc Ki67=Ki67.svs \
--output model.pkl \
--config configs/default.yaml \
--mpp 0.25
If you omit
marker=, the marker name is derived from the last token of the filename stem.hisalign registeronly producesmodel.pklby default; setgenerate_report: truein the config to also emitviz_report.html.
2. Warp coordinates with the model
hisalign warp \
--model model.pkl \
--marker CD3 \
--direction he_to_ihc \
--coords coords.csv \
--output mapped.csv
The input CSV must contain x and y columns; the output adds marker and direction columns.
3. Generate visualizations from an existing model
hisalign visualize \
--model model.pkl \
--output-dir ./out \
--config configs/default.yaml
Default behavior:
hisalign registeronly writesmodel.pklby default. Setgenerate_report: truein your config (or inconfigs/default.yaml) to also produce the unifiedviz_report.html.
register also writes viz_report.html automatically when visualization is enabled.
🖼️ Works with Plain Images Too
The repo includes whole-slide thumbnail exports of an H&E slide and a CD3 IHC slide (examples/images/he.jpg and examples/images/ihc.jpg) for a quick demo:
python examples/register_jpg.py --output-dir ./out
For synthetic data:
python examples/register_jpg.py --synthetic --output-dir ./out
Or provide your own images:
python examples/register_jpg.py --he he.jpg --ihc ihc.jpg --output-dir ./out
📐 Coordinate Convention
⚠️ All coordinates are level-0 (highest resolution) pixel coordinates.
- Order is
(x, y)=(column, row). - Origin is the top-left corner.
- For coordinates from another pyramid level, scale them to level-0 first.
📦 Supported Formats
| Type | Formats |
|---|---|
| KFBio native | .kfb |
| OpenSlide | .svs, .tif/.tiff, .ndpi, .vms/.vmu, .mrxs, .scn |
| Plain images | .jpg, .jpeg, .png, .bmp |
📤 Outputs
After running hisalign register:
model.pkl— serializable alignment model containing all spatial transforms; no original slides required afterwards.
If visualization is enabled, the same directory also contains:
viz_report.html— unified visualization report: a single self-contained web page with two tabs. The "Slide Summary" tab shows overlay comparisons, rTRE statistics, per-marker thumbnails, and deformation fields. The "Patch Gallery" tab shows the sampled patches with global context and local HE/IHC patch comparisons. Images are JPEG-compressed and web-sized so the file stays small enough to open and share.
Tip: The default
configs/default.yamlhasgenerate_report: false. Set it totrueto getviz_report.htmldirectly fromhisalign registerwithout runninghisalign visualize.
⚙️ Configuration
Key items in configs/default.yaml:
registration_level: 3 # pyramid level used for registration
max_image_dim_px: 1024 # longest side of the processed registration image
preprocessing: "od" # "od" optical density | "gray" simple grayscale
feature_detector: "kaze" # kaze / akaze / sift / orb / brisk
feature_n_levels: 3
match_max_ratio: 1.0 # Lowe ratio test; 1.0 disables
mpp: null # level-0 pixel size (µm/px); set when metadata is missing
# Visualization
viz_sample_n: 8 # number of patches in gallery, 0 to disable
viz_global_thumb_max_dim_px: 512 # longest side of global location thumbnails
viz_image_format: "jpeg" # png | jpeg — base format for most figures
viz_image_quality: 85 # JPEG quality (0-100), ignored for PNG
viz_overlay_dpi: 80 # DPI for overlay figures
viz_thumb_dpi: 80 # DPI for per-marker thumbnails
viz_deformation_dpi: 60 # DPI for deformation field plots
viz_patch_dpi: 80 # DPI for patch gallery figures
viz_patch_image_format: "jpeg" # format specifically for patch gallery
viz_patch_image_quality: 85 # JPEG quality for patch gallery
viz_patch_max_px: 512 # local patches are resized to this longest side before plotting
viz_patch_col_inches: 2.5 # width per marker column in patch gallery figures
generate_report: false # whether to generate viz_report.html
report_rtre_threshold: 5.0 # rTRE threshold for "good" highlighting
🗂️ Project Structure
hisalign/
├── README.md
├── README.zh.md
├── pyproject.toml
├── configs/default.yaml
├── examples/
│ ├── register_jpg.py
│ └── images/
│ ├── he.jpg
│ └── ihc.jpg
├── src/hisalign/
│ ├── api.py
│ ├── cli.py
│ ├── preprocessing.py
│ ├── registration/
│ ├── slide_io/
│ └── viz.py
└── tests/
🧪 Try It
The fastest way to verify the installation and see HISAlign in action is to run the bundled example:
python examples/register_jpg.py --output-dir ./out
This registers the real whole-slide thumbnails in examples/images/ and produces:
out/model.pklout/00_unregistered.pngout/01_rigid.pngout/02_nonrigid.png
Example Results
After running the command above, open the generated overlays:
out/00_unregistered.png— green/magenta overlay before registration (structures are shifted).out/01_rigid.png— overlay after rigid registration.out/02_nonrigid.png— overlay after non-rigid registration; overlapping structures turn white/gray.
Green = H&E, magenta = CD3 IHC.
🙋 Author
Created with 💙 by Yifan Feng
📧 evanfeng97@gmail.com
📚 References
- VALIS: Virtual Alignment of pathology Image Series
- DISK: Learning local features with policy gradient (Tyszkiewicz et al., NeurIPS 2020)
- LightGlue: Local Feature Matching at Light Speed (Lindenberger et al., CVPR 2023)
Making whole-slide alignment as intuitive as solving a jigsaw puzzle.
Project details
Release history Release notifications | RSS feed
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 hisalign-0.2.0.tar.gz.
File metadata
- Download URL: hisalign-0.2.0.tar.gz
- Upload date:
- Size: 2.1 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
301675f5dca69082f149d7c7d28b6f7d2fc4389b4970d8a4aa24fd0a0804b081
|
|
| MD5 |
c79611185a7eb061f9361faa69dbabc2
|
|
| BLAKE2b-256 |
a8e83083cb1f0b1f2d915887d82cef87e350e49246b86c799ff7a4f732a96e5e
|
Provenance
The following attestation bundles were made for hisalign-0.2.0.tar.gz:
Publisher:
publish.yml on yifanfeng97/hisalign
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
hisalign-0.2.0.tar.gz -
Subject digest:
301675f5dca69082f149d7c7d28b6f7d2fc4389b4970d8a4aa24fd0a0804b081 - Sigstore transparency entry: 1896503590
- Sigstore integration time:
-
Permalink:
yifanfeng97/hisalign@980ad839f236e76f503130b28a1d840ea97473bb -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/yifanfeng97
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@980ad839f236e76f503130b28a1d840ea97473bb -
Trigger Event:
release
-
Statement type:
File details
Details for the file hisalign-0.2.0-py3-none-any.whl.
File metadata
- Download URL: hisalign-0.2.0-py3-none-any.whl
- Upload date:
- Size: 52.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e81b1fb4eccdaf7ec2b2ce2987e94ac22f877eaffb007180e8f0b55b2900ba09
|
|
| MD5 |
8be1b0393354cbcae8c367adf1398fee
|
|
| BLAKE2b-256 |
3db7293f2e80077d58b55bcc8072d1174d8fc139001106eee80f7df54b352289
|
Provenance
The following attestation bundles were made for hisalign-0.2.0-py3-none-any.whl:
Publisher:
publish.yml on yifanfeng97/hisalign
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
hisalign-0.2.0-py3-none-any.whl -
Subject digest:
e81b1fb4eccdaf7ec2b2ce2987e94ac22f877eaffb007180e8f0b55b2900ba09 - Sigstore transparency entry: 1896503807
- Sigstore integration time:
-
Permalink:
yifanfeng97/hisalign@980ad839f236e76f503130b28a1d840ea97473bb -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/yifanfeng97
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@980ad839f236e76f503130b28a1d840ea97473bb -
Trigger Event:
release
-
Statement type: