Metadata-Version: 2.5
Name: tmapslide
Version: 0.2.2
Summary: Pure Python UNIC TMAP whole-slide image reader with OpenSlide-compatible API
Project-URL: Homepage, https://github.com/yifanfeng97/tmapslide
Project-URL: Documentation, https://github.com/yifanfeng97/tmapslide#readme
Project-URL: Repository, https://github.com/yifanfeng97/tmapslide
Project-URL: Issues, https://github.com/yifanfeng97/tmapslide/issues
Author-email: Yifan Feng <evanfeng97@gmail.com>
License-Expression: MIT
License-File: LICENSE
Keywords: digital-pathology,openslide,pathology,tmap,tmapslide,unic,whole-slide-image,wsi
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering :: Image Processing
Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
Requires-Python: >=3.10
Requires-Dist: pillow>=9.0.0
Provides-Extra: dev
Requires-Dist: mypy; extra == 'dev'
Requires-Dist: pytest-cov; extra == 'dev'
Requires-Dist: pytest>=7.0; extra == 'dev'
Requires-Dist: ruff; extra == 'dev'
Description-Content-Type: text/markdown

<div align="center">

# TmapSlide

**Pure Python reader for UNIC TMAP whole-slide images — no SDK, no native deps.**

*以纯 Python 读取联影 TMAP 全切片图像，开箱即用*

<p align="center">
  <a href="https://pypi.org/project/tmapslide/">
    <img src="https://img.shields.io/pypi/v/tmapslide?style=for-the-badge&logo=pypi&logoColor=white&labelColor=1a1a2e&color=3776ab" alt="PyPI Version">
  </a>
  <a href="https://pypi.org/project/tmapslide/">
    <img src="https://img.shields.io/pypi/dm/tmapslide?style=for-the-badge&logo=pypi&logoColor=white&labelColor=1a1a2e&color=3776ab" alt="PyPI Downloads">
  </a>
  <a href="https://python.org">
    <img src="https://img.shields.io/badge/python-3.10%2B-3776ab?style=for-the-badge&logo=python&logoColor=white&labelColor=1a1a2e" alt="Python Version">
  </a>
  <a href="LICENSE">
    <img src="https://img.shields.io/badge/license-MIT-06b6d4?style=for-the-badge&logo=openaccess&logoColor=white&labelColor=1a1a2e" alt="License">
  </a>
  <a href="https://github.com/yifanfeng97/tmapslide/stargazers">
    <img src="https://img.shields.io/github/stars/yifanfeng97/tmapslide?style=for-the-badge&logo=github&labelColor=1a1a2e&color=facc15" alt="GitHub Stars">
  </a>
</p>

[📖 English](#-quick-start) · [中文说明](#-中文说明)

<br/>

<img src="docs/hero.jpg" alt="TmapSlide — UNIC TMAP whole-slide images in pure Python" width="800" style="max-width: 100%;">

</div>

## ⚡ Quick Start

**1. Install:**

```bash
pip install tmapslide
```

**2. Read a slide:**

```python
import tmapslide

slide = tmapslide.OpenSlide("sample.TMAP")

print(slide.dimensions)          # (71424, 72704)
print(slide.level_count)         # 10
print(slide.level_downsamples)   # (1.0, 2.0, 4.0, ...)

region = slide.read_region((512, 512), 0, (1024, 1024))  # RGBA PIL image
thumb = slide.get_thumbnail((512, 512))
macro = slide.associated_images["macro"]
```

## ✨ Features

- **Pure Python** — no vendor SDK, no native dependencies; only Pillow
- **OpenSlide-compatible API** — drop-in for code written against
  `openslide` / `kfbslide`: `read_region`, `get_thumbnail`, `dimensions`,
  `level_count`, `level_dimensions`, `level_downsamples`, `properties`,
  `associated_images`
- **Both known TMAP variants** — `TMAP06` (3-level pyramid) and
  `TMAP07` (up to 10 levels)
- **Multi-file slides** — TMAP06 slides that spill tiles into `.DT1`
  sidecar files are read transparently
- **Trusted pixel size** — several TMAP06 *and* TMAP07 headers carry an
  impossible `pixel_size` (e.g. 6.88e-05 mm at 40x, implying ~2 µm
  nuclei); tmapslide cross-checks it against the objective power and
  falls back to 10 µm / magnification, exposing the result via
  `openslide.mpp-x` / `openslide.mpp-y` (the raw header value stays in
  `tmap.pixel_size_mm`, the decision in `tmap.mpp_source`)
- **Fork-safe file handles** — safe with PyTorch `DataLoader` workers
- **LRU decoded-tile cache** — fast repeated reads
- **Thread-safe reads** — concurrent `read_region` from worker threads

## 🏎️ Performance

Benchmark vs [ASlide](https://github.com/MrPeterJin/ASlide)'s pure-Python
TMAP backend (median of 5 runs, same files, same machine):

| Scenario | TMAP07 | TMAP06 |
|---|---|---|
| Open slide | **281 ms** vs 43 ms ⚠️ | **101 ms** vs 224 ms (2.2×) |
| Cold 1024² region @L0 | **4.1 ms** vs 15.7 ms (3.8×) | **2.6 ms** vs 14.8 ms (5.6×) |
| Random 512² region @L0 | **1.1 ms** vs 5.7 ms (5.3×) | **0.9 ms** vs 6.3 ms (6.7×) |
| Warm 512² region ×50 | **42 ms** vs 217 ms (5.1×) | **54 ms** vs 408 ms (7.6×) |

> ASlide's TMAP backend re-decodes every tile on every call; tmapslide adds
> an LRU decoded-tile cache and per-tile culling, so warm reads and random
> access are several times faster.

## 📖 API

### `tmapslide.OpenSlide(filename)`

| Member | Description |
| --- | --- |
| `dimensions` | `(width, height)` at level 0 |
| `level_count` | number of pyramid levels |
| `level_dimensions` | `(w, h)` per level |
| `level_downsamples` | downsample factor per level |
| `properties` | read-only metadata mapping (`openslide.vendor=unic`, `openslide.mpp-x/y`, `tmap.*`) |
| `associated_images` | lazy mapping, typically `macro` / `label` / `thumbnail` |
| `read_region(loc, level, size)` | `PIL.Image` (RGBA) of the region |
| `get_thumbnail(size)` | stored thumbnail when available, else lowest level |
| `get_best_level_for_downsample(ds)` | best level for a downsample factor |
| `iter_tiles(level=0)` | yields `(x, y, load)` per stored tile |
| `close()` / context manager | release resources |

### `tmapslide.open_slide(filename)`

Alias of `OpenSlide(filename)`.

## 📦 Supported Formats

| Format | Extension | Vendor | Backend |
| --- | --- | --- | --- |
| TMAP 06 | `.TMAP` (+ optional `.DT1` sidecars) | UNIC (United Imaging) | Pure Python |
| TMAP 07 | `.TMAP` | UNIC (United Imaging) | Pure Python |

## 🧪 Testing

Tests run against real TMAP samples when the `scce_external_center` data
directory (or `TAPSLIDE_TEST_DATA`) is present next to the repo; synthetic
fixtures keep the core parser covered everywhere else.

```bash
pip install -e .[dev]
pytest
```

## 📄 Format Notes (reverse-engineered)

TMAP is an undocumented proprietary format. This reader is built from
binary analysis of real scanner output, cross-validated against
[ASlide](https://github.com/MrPeterJin/ASlide). Both variants store plain
JPEG tiles with a small binary header and index tables; there is no
encryption.

- TMAP06 stores 3 pyramid levels (40x / 10x / 2.5x); TMAP07 stores up to
  10 levels (40x down to 0.078x, halving each level).
- TMAP06 level-2 previews come from pre-rendered `ShrinkTile` entries and
  are JPEG-compressed at that scale.
- `iter_tiles()` exposes the stored tile grid directly — useful for
  tile-based ML pipelines.
- **Metadata caveat**: the header `pixel_size` field cannot be trusted.
  Measured on real slides (cell-nucleus diameters, canvas physical size,
  cross-checked with ASlide), both the Henan TMAP06 batch (6.88e-05 mm)
  and the Shanxi TMAP07 batch (1.01e-04 mm) carry corrupt values at 40x;
  the true resolution is 0.25 µm/px. tmapslide keeps the raw value in
  `tmap.pixel_size_mm`, exposes the corrected one via
  `openslide.mpp-x/y`, and records the decision in `tmap.mpp_source`
  (`header` / `derived`).

## 📄 License

[MIT](LICENSE)

## 🙏 Acknowledgments

- [kfbslide](https://github.com/yifanfeng97/kfbslide) — the KFB reader this
  project is modelled after
- [OpenSlide](https://openslide.org/) — the API this library mimics
- [ASlide](https://github.com/MrPeterJin/ASlide) — its independent
  reverse-engineering of the TMAP06 layer/block structures (from decompiled
  vendor SDK) was used to cross-validate this implementation. tmapslide is
  an independent MIT-licensed implementation and ships no ASlide code
