tmapslide 0.2.0__tar.gz → 0.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tmapslide-0.2.1/PKG-INFO +188 -0
- tmapslide-0.2.1/README.md +157 -0
- {tmapslide-0.2.0 → tmapslide-0.2.1}/pyproject.toml +1 -1
- {tmapslide-0.2.0 → tmapslide-0.2.1}/src/tmapslide/__init__.py +1 -1
- {tmapslide-0.2.0 → tmapslide-0.2.1}/src/tmapslide/_slide.py +29 -6
- tmapslide-0.2.0/PKG-INFO +0 -142
- tmapslide-0.2.0/README.md +0 -111
- {tmapslide-0.2.0 → tmapslide-0.2.1}/.gitignore +0 -0
- {tmapslide-0.2.0 → tmapslide-0.2.1}/LICENSE +0 -0
- {tmapslide-0.2.0 → tmapslide-0.2.1}/examples/read_region.py +0 -0
- {tmapslide-0.2.0 → tmapslide-0.2.1}/src/tmapslide/_cache.py +0 -0
- {tmapslide-0.2.0 → tmapslide-0.2.1}/src/tmapslide/_exceptions.py +0 -0
- {tmapslide-0.2.0 → tmapslide-0.2.1}/src/tmapslide/_tmapformat.py +0 -0
- {tmapslide-0.2.0 → tmapslide-0.2.1}/tests/test_basic.py +0 -0
- {tmapslide-0.2.0 → tmapslide-0.2.1}/tests/test_synthetic.py +0 -0
- {tmapslide-0.2.0 → tmapslide-0.2.1}/tests/tmap_fixtures.py +0 -0
tmapslide-0.2.1/PKG-INFO
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: tmapslide
|
|
3
|
+
Version: 0.2.1
|
|
4
|
+
Summary: Pure Python UNIC TMAP whole-slide image reader with OpenSlide-compatible API
|
|
5
|
+
Project-URL: Homepage, https://github.com/yifanfeng97/tmapslide
|
|
6
|
+
Project-URL: Documentation, https://github.com/yifanfeng97/tmapslide#readme
|
|
7
|
+
Project-URL: Repository, https://github.com/yifanfeng97/tmapslide
|
|
8
|
+
Project-URL: Issues, https://github.com/yifanfeng97/tmapslide/issues
|
|
9
|
+
Author-email: Yifan Feng <evanfeng97@gmail.com>
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: digital-pathology,openslide,pathology,tmap,tmapslide,unic,whole-slide-image,wsi
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering :: Image Processing
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Requires-Dist: pillow>=9.0.0
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: mypy; extra == 'dev'
|
|
27
|
+
Requires-Dist: pytest-cov; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
29
|
+
Requires-Dist: ruff; extra == 'dev'
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
<div align="center">
|
|
33
|
+
|
|
34
|
+
<img src="docs/hero.jpg" alt="TmapSlide — UNIC TMAP whole-slide images in pure Python" width="800" style="max-width: 100%;">
|
|
35
|
+
|
|
36
|
+
<br/>
|
|
37
|
+
|
|
38
|
+
**TmapSlide**
|
|
39
|
+
|
|
40
|
+
**Pure Python reader for UNIC TMAP whole-slide images — no SDK, no native deps.**
|
|
41
|
+
|
|
42
|
+
*以纯 Python 读取联影 TMAP 全切片图像,开箱即用*
|
|
43
|
+
|
|
44
|
+
<p align="center">
|
|
45
|
+
<a href="https://pypi.org/project/tmapslide/">
|
|
46
|
+
<img src="https://img.shields.io/pypi/v/tmapslide?style=for-the-badge&logo=pypi&logoColor=white&labelColor=1a1a2e&color=3776ab" alt="PyPI Version">
|
|
47
|
+
</a>
|
|
48
|
+
<a href="https://pypi.org/project/tmapslide/">
|
|
49
|
+
<img src="https://img.shields.io/pypi/dm/tmapslide?style=for-the-badge&logo=pypi&logoColor=white&labelColor=1a1a2e&color=3776ab" alt="PyPI Downloads">
|
|
50
|
+
</a>
|
|
51
|
+
<a href="https://python.org">
|
|
52
|
+
<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">
|
|
53
|
+
</a>
|
|
54
|
+
<a href="LICENSE">
|
|
55
|
+
<img src="https://img.shields.io/badge/license-MIT-06b6d4?style=for-the-badge&logo=openaccess&logoColor=white&labelColor=1a1a2e" alt="License">
|
|
56
|
+
</a>
|
|
57
|
+
<a href="https://github.com/yifanfeng97/tmapslide/actions/workflows/test.yml">
|
|
58
|
+
<img src="https://img.shields.io/github/actions/yifanfeng97/tmapslide/test.yml?branch=main&style=for-the-badge&logo=githubactions&logoColor=white&labelColor=1a1a2e&label=tests" alt="Tests">
|
|
59
|
+
</a>
|
|
60
|
+
<a href="https://github.com/yifanfeng97/tmapslide/stargazers">
|
|
61
|
+
<img src="https://img.shields.io/github/stars/yifanfeng97/tmapslide?style=for-the-badge&logo=github&labelColor=1a1a2e&color=facc15" alt="GitHub Stars">
|
|
62
|
+
</a>
|
|
63
|
+
</p>
|
|
64
|
+
|
|
65
|
+
[📖 English](#-quick-start) · [中文说明](#-中文说明)
|
|
66
|
+
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
## ⚡ Quick Start
|
|
70
|
+
|
|
71
|
+
**1. Install:**
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
pip install tmapslide
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**2. Read a slide:**
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
import tmapslide
|
|
81
|
+
|
|
82
|
+
slide = tmapslide.OpenSlide("sample.TMAP")
|
|
83
|
+
|
|
84
|
+
print(slide.dimensions) # (71424, 72704)
|
|
85
|
+
print(slide.level_count) # 10
|
|
86
|
+
print(slide.level_downsamples) # (1.0, 2.0, 4.0, ...)
|
|
87
|
+
|
|
88
|
+
region = slide.read_region((512, 512), 0, (1024, 1024)) # RGBA PIL image
|
|
89
|
+
thumb = slide.get_thumbnail((512, 512))
|
|
90
|
+
macro = slide.associated_images["macro"]
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## ✨ Features
|
|
94
|
+
|
|
95
|
+
- **Pure Python** — no vendor SDK, no native dependencies; only Pillow
|
|
96
|
+
- **OpenSlide-compatible API** — drop-in for code written against
|
|
97
|
+
`openslide` / `kfbslide`: `read_region`, `get_thumbnail`, `dimensions`,
|
|
98
|
+
`level_count`, `level_dimensions`, `level_downsamples`, `properties`,
|
|
99
|
+
`associated_images`
|
|
100
|
+
- **Both known TMAP variants** — `TMAP06` (3-level pyramid) and
|
|
101
|
+
`TMAP07` (up to 10 levels)
|
|
102
|
+
- **Fork-safe file handles** — safe with PyTorch `DataLoader` workers
|
|
103
|
+
- **LRU decoded-tile cache** — fast repeated reads
|
|
104
|
+
- **Thread-safe reads** — concurrent `read_region` from worker threads
|
|
105
|
+
|
|
106
|
+
## 🏎️ Performance
|
|
107
|
+
|
|
108
|
+
Benchmark vs [ASlide](https://github.com/MrPeterJin/ASlide)'s pure-Python
|
|
109
|
+
TMAP backend (median of 5 runs, same files, same machine):
|
|
110
|
+
|
|
111
|
+
| Scenario | TMAP07 | TMAP06 |
|
|
112
|
+
|---|---|---|
|
|
113
|
+
| Open slide | **281 ms** vs 43 ms ⚠️ | **101 ms** vs 224 ms (2.2×) |
|
|
114
|
+
| Cold 1024² region @L0 | **4.1 ms** vs 15.7 ms (3.8×) | **2.6 ms** vs 14.8 ms (5.6×) |
|
|
115
|
+
| Random 512² region @L0 | **1.1 ms** vs 5.7 ms (5.3×) | **0.9 ms** vs 6.3 ms (6.7×) |
|
|
116
|
+
| Warm 512² region ×50 | **42 ms** vs 217 ms (5.1×) | **54 ms** vs 408 ms (7.6×) |
|
|
117
|
+
|
|
118
|
+
> ASlide's TMAP backend re-decodes every tile on every call; tmapslide adds
|
|
119
|
+
> an LRU decoded-tile cache and per-tile culling, so warm reads and random
|
|
120
|
+
> access are several times faster.
|
|
121
|
+
|
|
122
|
+
## 📖 API
|
|
123
|
+
|
|
124
|
+
### `tmapslide.OpenSlide(filename)`
|
|
125
|
+
|
|
126
|
+
| Member | Description |
|
|
127
|
+
| --- | --- |
|
|
128
|
+
| `dimensions` | `(width, height)` at level 0 |
|
|
129
|
+
| `level_count` | number of pyramid levels |
|
|
130
|
+
| `level_dimensions` | `(w, h)` per level |
|
|
131
|
+
| `level_downsamples` | downsample factor per level |
|
|
132
|
+
| `properties` | read-only metadata mapping (`openslide.vendor=unic`, `tmap.*`) |
|
|
133
|
+
| `associated_images` | lazy mapping, typically `macro` / `label` / `thumbnail` |
|
|
134
|
+
| `read_region(loc, level, size)` | `PIL.Image` (RGBA) of the region |
|
|
135
|
+
| `get_thumbnail(size)` | thumbnail from the lowest resolution level |
|
|
136
|
+
| `get_best_level_for_downsample(ds)` | best level for a downsample factor |
|
|
137
|
+
| `close()` / context manager | release resources |
|
|
138
|
+
|
|
139
|
+
### `tmapslide.open_slide(filename)`
|
|
140
|
+
|
|
141
|
+
Alias of `OpenSlide(filename)`.
|
|
142
|
+
|
|
143
|
+
## 📦 Supported Formats
|
|
144
|
+
|
|
145
|
+
| Format | Extension | Vendor | Backend |
|
|
146
|
+
| --- | --- | --- | --- |
|
|
147
|
+
| TMAP 06 | `.TMAP` | UNIC (United Imaging) | Pure Python |
|
|
148
|
+
| TMAP 07 | `.TMAP` | UNIC (United Imaging) | Pure Python |
|
|
149
|
+
|
|
150
|
+
## 🧪 Testing
|
|
151
|
+
|
|
152
|
+
Tests run against real TMAP samples when the `scce_external_center` data
|
|
153
|
+
directory (or `TAPSLIDE_TEST_DATA`) is present next to the repo; synthetic
|
|
154
|
+
fixtures keep the core parser covered everywhere else.
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
pip install -e .[dev]
|
|
158
|
+
pytest
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## 📄 Format Notes (reverse-engineered)
|
|
162
|
+
|
|
163
|
+
TMAP is an undocumented proprietary format. This reader is built from
|
|
164
|
+
binary analysis of real scanner output, cross-validated against
|
|
165
|
+
[ASlide](https://github.com/MrPeterJin/ASlide). Both variants store plain
|
|
166
|
+
JPEG tiles with a small binary header and index tables; there is no
|
|
167
|
+
encryption.
|
|
168
|
+
|
|
169
|
+
- TMAP06 stores 3 pyramid levels (40x / 10x / 2.5x); TMAP07 stores up to
|
|
170
|
+
10 levels (40x down to 0.078x, halving each level).
|
|
171
|
+
- TMAP06 level-2 previews come from pre-rendered `ShrinkTile` entries and
|
|
172
|
+
are JPEG-compressed at that scale.
|
|
173
|
+
- `iter_tiles()` exposes the stored tile grid directly — useful for
|
|
174
|
+
tile-based ML pipelines.
|
|
175
|
+
|
|
176
|
+
## 📄 License
|
|
177
|
+
|
|
178
|
+
[MIT](LICENSE)
|
|
179
|
+
|
|
180
|
+
## 🙏 Acknowledgments
|
|
181
|
+
|
|
182
|
+
- [kfbslide](https://github.com/yifanfeng97/kfbslide) — the KFB reader this
|
|
183
|
+
project is modelled after
|
|
184
|
+
- [OpenSlide](https://openslide.org/) — the API this library mimics
|
|
185
|
+
- [ASlide](https://github.com/MrPeterJin/ASlide) — its independent
|
|
186
|
+
reverse-engineering of the TMAP06 layer/block structures (from decompiled
|
|
187
|
+
vendor SDK) was used to cross-validate this implementation. tmapslide is
|
|
188
|
+
an independent MIT-licensed implementation and ships no ASlide code
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
<img src="docs/hero.jpg" alt="TmapSlide — UNIC TMAP whole-slide images in pure Python" width="800" style="max-width: 100%;">
|
|
4
|
+
|
|
5
|
+
<br/>
|
|
6
|
+
|
|
7
|
+
**TmapSlide**
|
|
8
|
+
|
|
9
|
+
**Pure Python reader for UNIC TMAP whole-slide images — no SDK, no native deps.**
|
|
10
|
+
|
|
11
|
+
*以纯 Python 读取联影 TMAP 全切片图像,开箱即用*
|
|
12
|
+
|
|
13
|
+
<p align="center">
|
|
14
|
+
<a href="https://pypi.org/project/tmapslide/">
|
|
15
|
+
<img src="https://img.shields.io/pypi/v/tmapslide?style=for-the-badge&logo=pypi&logoColor=white&labelColor=1a1a2e&color=3776ab" alt="PyPI Version">
|
|
16
|
+
</a>
|
|
17
|
+
<a href="https://pypi.org/project/tmapslide/">
|
|
18
|
+
<img src="https://img.shields.io/pypi/dm/tmapslide?style=for-the-badge&logo=pypi&logoColor=white&labelColor=1a1a2e&color=3776ab" alt="PyPI Downloads">
|
|
19
|
+
</a>
|
|
20
|
+
<a href="https://python.org">
|
|
21
|
+
<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">
|
|
22
|
+
</a>
|
|
23
|
+
<a href="LICENSE">
|
|
24
|
+
<img src="https://img.shields.io/badge/license-MIT-06b6d4?style=for-the-badge&logo=openaccess&logoColor=white&labelColor=1a1a2e" alt="License">
|
|
25
|
+
</a>
|
|
26
|
+
<a href="https://github.com/yifanfeng97/tmapslide/actions/workflows/test.yml">
|
|
27
|
+
<img src="https://img.shields.io/github/actions/yifanfeng97/tmapslide/test.yml?branch=main&style=for-the-badge&logo=githubactions&logoColor=white&labelColor=1a1a2e&label=tests" alt="Tests">
|
|
28
|
+
</a>
|
|
29
|
+
<a href="https://github.com/yifanfeng97/tmapslide/stargazers">
|
|
30
|
+
<img src="https://img.shields.io/github/stars/yifanfeng97/tmapslide?style=for-the-badge&logo=github&labelColor=1a1a2e&color=facc15" alt="GitHub Stars">
|
|
31
|
+
</a>
|
|
32
|
+
</p>
|
|
33
|
+
|
|
34
|
+
[📖 English](#-quick-start) · [中文说明](#-中文说明)
|
|
35
|
+
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
## ⚡ Quick Start
|
|
39
|
+
|
|
40
|
+
**1. Install:**
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pip install tmapslide
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**2. Read a slide:**
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
import tmapslide
|
|
50
|
+
|
|
51
|
+
slide = tmapslide.OpenSlide("sample.TMAP")
|
|
52
|
+
|
|
53
|
+
print(slide.dimensions) # (71424, 72704)
|
|
54
|
+
print(slide.level_count) # 10
|
|
55
|
+
print(slide.level_downsamples) # (1.0, 2.0, 4.0, ...)
|
|
56
|
+
|
|
57
|
+
region = slide.read_region((512, 512), 0, (1024, 1024)) # RGBA PIL image
|
|
58
|
+
thumb = slide.get_thumbnail((512, 512))
|
|
59
|
+
macro = slide.associated_images["macro"]
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## ✨ Features
|
|
63
|
+
|
|
64
|
+
- **Pure Python** — no vendor SDK, no native dependencies; only Pillow
|
|
65
|
+
- **OpenSlide-compatible API** — drop-in for code written against
|
|
66
|
+
`openslide` / `kfbslide`: `read_region`, `get_thumbnail`, `dimensions`,
|
|
67
|
+
`level_count`, `level_dimensions`, `level_downsamples`, `properties`,
|
|
68
|
+
`associated_images`
|
|
69
|
+
- **Both known TMAP variants** — `TMAP06` (3-level pyramid) and
|
|
70
|
+
`TMAP07` (up to 10 levels)
|
|
71
|
+
- **Fork-safe file handles** — safe with PyTorch `DataLoader` workers
|
|
72
|
+
- **LRU decoded-tile cache** — fast repeated reads
|
|
73
|
+
- **Thread-safe reads** — concurrent `read_region` from worker threads
|
|
74
|
+
|
|
75
|
+
## 🏎️ Performance
|
|
76
|
+
|
|
77
|
+
Benchmark vs [ASlide](https://github.com/MrPeterJin/ASlide)'s pure-Python
|
|
78
|
+
TMAP backend (median of 5 runs, same files, same machine):
|
|
79
|
+
|
|
80
|
+
| Scenario | TMAP07 | TMAP06 |
|
|
81
|
+
|---|---|---|
|
|
82
|
+
| Open slide | **281 ms** vs 43 ms ⚠️ | **101 ms** vs 224 ms (2.2×) |
|
|
83
|
+
| Cold 1024² region @L0 | **4.1 ms** vs 15.7 ms (3.8×) | **2.6 ms** vs 14.8 ms (5.6×) |
|
|
84
|
+
| Random 512² region @L0 | **1.1 ms** vs 5.7 ms (5.3×) | **0.9 ms** vs 6.3 ms (6.7×) |
|
|
85
|
+
| Warm 512² region ×50 | **42 ms** vs 217 ms (5.1×) | **54 ms** vs 408 ms (7.6×) |
|
|
86
|
+
|
|
87
|
+
> ASlide's TMAP backend re-decodes every tile on every call; tmapslide adds
|
|
88
|
+
> an LRU decoded-tile cache and per-tile culling, so warm reads and random
|
|
89
|
+
> access are several times faster.
|
|
90
|
+
|
|
91
|
+
## 📖 API
|
|
92
|
+
|
|
93
|
+
### `tmapslide.OpenSlide(filename)`
|
|
94
|
+
|
|
95
|
+
| Member | Description |
|
|
96
|
+
| --- | --- |
|
|
97
|
+
| `dimensions` | `(width, height)` at level 0 |
|
|
98
|
+
| `level_count` | number of pyramid levels |
|
|
99
|
+
| `level_dimensions` | `(w, h)` per level |
|
|
100
|
+
| `level_downsamples` | downsample factor per level |
|
|
101
|
+
| `properties` | read-only metadata mapping (`openslide.vendor=unic`, `tmap.*`) |
|
|
102
|
+
| `associated_images` | lazy mapping, typically `macro` / `label` / `thumbnail` |
|
|
103
|
+
| `read_region(loc, level, size)` | `PIL.Image` (RGBA) of the region |
|
|
104
|
+
| `get_thumbnail(size)` | thumbnail from the lowest resolution level |
|
|
105
|
+
| `get_best_level_for_downsample(ds)` | best level for a downsample factor |
|
|
106
|
+
| `close()` / context manager | release resources |
|
|
107
|
+
|
|
108
|
+
### `tmapslide.open_slide(filename)`
|
|
109
|
+
|
|
110
|
+
Alias of `OpenSlide(filename)`.
|
|
111
|
+
|
|
112
|
+
## 📦 Supported Formats
|
|
113
|
+
|
|
114
|
+
| Format | Extension | Vendor | Backend |
|
|
115
|
+
| --- | --- | --- | --- |
|
|
116
|
+
| TMAP 06 | `.TMAP` | UNIC (United Imaging) | Pure Python |
|
|
117
|
+
| TMAP 07 | `.TMAP` | UNIC (United Imaging) | Pure Python |
|
|
118
|
+
|
|
119
|
+
## 🧪 Testing
|
|
120
|
+
|
|
121
|
+
Tests run against real TMAP samples when the `scce_external_center` data
|
|
122
|
+
directory (or `TAPSLIDE_TEST_DATA`) is present next to the repo; synthetic
|
|
123
|
+
fixtures keep the core parser covered everywhere else.
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
pip install -e .[dev]
|
|
127
|
+
pytest
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## 📄 Format Notes (reverse-engineered)
|
|
131
|
+
|
|
132
|
+
TMAP is an undocumented proprietary format. This reader is built from
|
|
133
|
+
binary analysis of real scanner output, cross-validated against
|
|
134
|
+
[ASlide](https://github.com/MrPeterJin/ASlide). Both variants store plain
|
|
135
|
+
JPEG tiles with a small binary header and index tables; there is no
|
|
136
|
+
encryption.
|
|
137
|
+
|
|
138
|
+
- TMAP06 stores 3 pyramid levels (40x / 10x / 2.5x); TMAP07 stores up to
|
|
139
|
+
10 levels (40x down to 0.078x, halving each level).
|
|
140
|
+
- TMAP06 level-2 previews come from pre-rendered `ShrinkTile` entries and
|
|
141
|
+
are JPEG-compressed at that scale.
|
|
142
|
+
- `iter_tiles()` exposes the stored tile grid directly — useful for
|
|
143
|
+
tile-based ML pipelines.
|
|
144
|
+
|
|
145
|
+
## 📄 License
|
|
146
|
+
|
|
147
|
+
[MIT](LICENSE)
|
|
148
|
+
|
|
149
|
+
## 🙏 Acknowledgments
|
|
150
|
+
|
|
151
|
+
- [kfbslide](https://github.com/yifanfeng97/kfbslide) — the KFB reader this
|
|
152
|
+
project is modelled after
|
|
153
|
+
- [OpenSlide](https://openslide.org/) — the API this library mimics
|
|
154
|
+
- [ASlide](https://github.com/MrPeterJin/ASlide) — its independent
|
|
155
|
+
reverse-engineering of the TMAP06 layer/block structures (from decompiled
|
|
156
|
+
vendor SDK) was used to cross-validate this implementation. tmapslide is
|
|
157
|
+
an independent MIT-licensed implementation and ships no ASlide code
|
|
@@ -408,6 +408,7 @@ class OpenSlide:
|
|
|
408
408
|
props = info.properties
|
|
409
409
|
img_w = int(props.get("tmap.img_width", 2448))
|
|
410
410
|
img_h = int(props.get("tmap.img_height", 2048))
|
|
411
|
+
tile_img_w, tile_img_h = info.__dict__.get("_tile_jpeg_size", (612, 512))
|
|
411
412
|
fh = self._ensure_open_handle()
|
|
412
413
|
|
|
413
414
|
# clamp level to what we can render
|
|
@@ -487,25 +488,47 @@ class OpenSlide:
|
|
|
487
488
|
n_x, n_y = blk["n_x"], blk["n_y"]
|
|
488
489
|
if n_x + img_w < x0_l0 or n_x > x0_l0 + w0 or n_y + img_h < y0_l0 or n_y > y0_l0 + h0:
|
|
489
490
|
continue
|
|
491
|
+
jw, jh = info.__dict__.get("_tile_jpeg_size", (tile_img_w, tile_img_h))
|
|
490
492
|
for layer_no, col, row, off, length in blk["tiles"]:
|
|
491
493
|
if layer_no != level:
|
|
492
494
|
continue
|
|
493
|
-
tile_img = self._get_cached_tile(off, length)
|
|
494
495
|
if layer_no > 0:
|
|
495
496
|
# one tile covers the whole block
|
|
496
497
|
tile_x0, tile_y0 = n_x, n_y
|
|
497
498
|
cover_w, cover_h = img_w, img_h
|
|
498
499
|
else:
|
|
499
|
-
tile_x0 = n_x + col *
|
|
500
|
-
tile_y0 = n_y + row *
|
|
501
|
-
cover_w, cover_h =
|
|
500
|
+
tile_x0 = n_x + col * jw
|
|
501
|
+
tile_y0 = n_y + row * jh
|
|
502
|
+
cover_w, cover_h = jw, jh
|
|
503
|
+
# cull tiles that miss the region BEFORE decoding
|
|
504
|
+
if (
|
|
505
|
+
tile_x0 + cover_w <= x0_l0
|
|
506
|
+
or tile_x0 >= x0_l0 + w0
|
|
507
|
+
or tile_y0 + cover_h <= y0_l0
|
|
508
|
+
or tile_y0 >= y0_l0 + h0
|
|
509
|
+
):
|
|
510
|
+
continue
|
|
511
|
+
tile_img = self._get_cached_tile(off, length)
|
|
502
512
|
paste_scaled(tile_img, tile_x0, tile_y0, cover_w, cover_h)
|
|
503
513
|
return out
|
|
504
514
|
|
|
505
515
|
def get_thumbnail(self, size: Tuple[int, int]) -> Image.Image:
|
|
506
|
-
"""Get a thumbnail image no larger than `size`.
|
|
516
|
+
"""Get a thumbnail image no larger than `size`.
|
|
517
|
+
|
|
518
|
+
Prefers the stored thumbnail associated image when the file
|
|
519
|
+
provides one; otherwise renders the lowest-resolution level.
|
|
520
|
+
"""
|
|
507
521
|
self._check_open()
|
|
508
|
-
|
|
522
|
+
names = set()
|
|
523
|
+
try:
|
|
524
|
+
names = set(self.associated_images)
|
|
525
|
+
except Exception:
|
|
526
|
+
names = set()
|
|
527
|
+
if "thumbnail" in names:
|
|
528
|
+
thumb = self.associated_images["thumbnail"].convert("RGB")
|
|
529
|
+
thumb.thumbnail(size, Image.LANCZOS)
|
|
530
|
+
return thumb
|
|
531
|
+
# Fall back to the lowest-resolution pyramid level.
|
|
509
532
|
level = self.level_count - 1
|
|
510
533
|
dims = self.level_dimensions[level]
|
|
511
534
|
region = self.read_region((0, 0), level, dims)
|
tmapslide-0.2.0/PKG-INFO
DELETED
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.5
|
|
2
|
-
Name: tmapslide
|
|
3
|
-
Version: 0.2.0
|
|
4
|
-
Summary: Pure Python UNIC TMAP whole-slide image reader with OpenSlide-compatible API
|
|
5
|
-
Project-URL: Homepage, https://github.com/yifanfeng97/tmapslide
|
|
6
|
-
Project-URL: Documentation, https://github.com/yifanfeng97/tmapslide#readme
|
|
7
|
-
Project-URL: Repository, https://github.com/yifanfeng97/tmapslide
|
|
8
|
-
Project-URL: Issues, https://github.com/yifanfeng97/tmapslide/issues
|
|
9
|
-
Author-email: Yifan Feng <evanfeng97@gmail.com>
|
|
10
|
-
License-Expression: MIT
|
|
11
|
-
License-File: LICENSE
|
|
12
|
-
Keywords: digital-pathology,openslide,pathology,tmap,tmapslide,unic,whole-slide-image,wsi
|
|
13
|
-
Classifier: Development Status :: 3 - Alpha
|
|
14
|
-
Classifier: Intended Audience :: Science/Research
|
|
15
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
-
Classifier: Programming Language :: Python :: 3
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
-
Classifier: Topic :: Scientific/Engineering :: Image Processing
|
|
22
|
-
Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
|
|
23
|
-
Requires-Python: >=3.10
|
|
24
|
-
Requires-Dist: pillow>=9.0.0
|
|
25
|
-
Provides-Extra: dev
|
|
26
|
-
Requires-Dist: mypy; extra == 'dev'
|
|
27
|
-
Requires-Dist: pytest-cov; extra == 'dev'
|
|
28
|
-
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
29
|
-
Requires-Dist: ruff; extra == 'dev'
|
|
30
|
-
Description-Content-Type: text/markdown
|
|
31
|
-
|
|
32
|
-
# TmapSlide
|
|
33
|
-
|
|
34
|
-

|
|
35
|
-
|
|
36
|
-
Pure Python reader for **UNIC TMAP** whole-slide images (WSI) with an
|
|
37
|
-
[OpenSlide](https://openslide.org/)-compatible API — the companion library to
|
|
38
|
-
[kfbslide](https://github.com/yifanfeng97/kfbslide) (KFB format).
|
|
39
|
-
|
|
40
|
-
```python
|
|
41
|
-
import tmapslide
|
|
42
|
-
|
|
43
|
-
slide = tmapslide.OpenSlide("sample.TMAP")
|
|
44
|
-
|
|
45
|
-
print(slide.dimensions) # (71424, 72704)
|
|
46
|
-
print(slide.level_count) # 10
|
|
47
|
-
print(slide.level_downsamples) # (1.0, 2.0, 4.0, ...)
|
|
48
|
-
|
|
49
|
-
region = slide.read_region((512, 512), 0, (1024, 1024)) # RGBA PIL image
|
|
50
|
-
thumb = slide.get_thumbnail((512, 512))
|
|
51
|
-
macro = slide.associated_images["macro"]
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
## Features
|
|
55
|
-
|
|
56
|
-
- **Pure Python** — no vendor SDK, no native dependencies; only Pillow.
|
|
57
|
-
- **OpenSlide-compatible API** — drop-in for code written against
|
|
58
|
-
`openslide`/`kfbslide`: `read_region`, `get_thumbnail`, `dimensions`,
|
|
59
|
-
`level_count`, `level_dimensions`, `level_downsamples`, `properties`,
|
|
60
|
-
`associated_images`.
|
|
61
|
-
- **Both known TMAP variants**: `TMAP06` (single pixel level, 612x512
|
|
62
|
-
overlapping tiles) and `TMAP07` (full pyramid, up to 10 levels, 256x256
|
|
63
|
-
tiles).
|
|
64
|
-
- **Fork-safe file handles** — safe to use from PyTorch `DataLoader` workers.
|
|
65
|
-
- LRU tile cache for fast repeated reads.
|
|
66
|
-
|
|
67
|
-
## Installation
|
|
68
|
-
|
|
69
|
-
```bash
|
|
70
|
-
pip install tmapslide
|
|
71
|
-
# or from source
|
|
72
|
-
pip install git+https://github.com/yifanfeng97/tmapslide.git
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
## Supported formats
|
|
76
|
-
|
|
77
|
-
| Format | Extension | Vendor | Backend |
|
|
78
|
-
| ------- | --------- | ------------------ | ----------- |
|
|
79
|
-
| TMAP 06 | `.TMAP` | UNIC (United Imaging) | Pure Python |
|
|
80
|
-
| TMAP 07 | `.TMAP` | UNIC (United Imaging) | Pure Python |
|
|
81
|
-
|
|
82
|
-
## API
|
|
83
|
-
|
|
84
|
-
### `tmapslide.OpenSlide(filename)`
|
|
85
|
-
|
|
86
|
-
| Member | Description |
|
|
87
|
-
| --------------------- | -------------------------------------------------- |
|
|
88
|
-
| `dimensions` | `(width, height)` at level 0 |
|
|
89
|
-
| `level_count` | number of pyramid levels |
|
|
90
|
-
| `level_dimensions` | `(w, h)` per level |
|
|
91
|
-
| `level_downsamples` | downsample factor per level |
|
|
92
|
-
| `properties` | read-only metadata mapping (`openslide.vendor=unic`, `tmap.*`) |
|
|
93
|
-
| `associated_images` | lazy mapping, typically `macro` / `label` |
|
|
94
|
-
| `read_region(loc, level, size)` | `PIL.Image` (RGBA) of the region |
|
|
95
|
-
| `get_thumbnail(size)` | thumbnail from the lowest resolution level |
|
|
96
|
-
| `get_best_level_for_downsample(ds)` | best level for a downsample factor |
|
|
97
|
-
| `close()` / context manager | release resources |
|
|
98
|
-
|
|
99
|
-
### `tmapslide.open_slide(filename)`
|
|
100
|
-
|
|
101
|
-
Alias of `OpenSlide(filename)`.
|
|
102
|
-
|
|
103
|
-
## Format notes (reverse-engineered)
|
|
104
|
-
|
|
105
|
-
TMAP is an undocumented proprietary format. This reader is built from
|
|
106
|
-
binary analysis of real scanner output, cross-validated against
|
|
107
|
-
[ASlide](https://github.com/MrPeterJin/ASlide). In short: both variants
|
|
108
|
-
store plain JPEG tiles with a small binary header and index tables; there
|
|
109
|
-
is no encryption.
|
|
110
|
-
|
|
111
|
-
- TMAP06 stores 3 pyramid levels (40x / 10x / 2.5x); TMAP07 stores up to
|
|
112
|
-
10 levels (40x down to 0.078x, halving each level).
|
|
113
|
-
- TMAP06 level-2 previews come from pre-rendered `ShrinkTile` entries and
|
|
114
|
-
are JPEG-compressed at that scale (slightly softer than downsampling
|
|
115
|
-
level 0 yourself).
|
|
116
|
-
- `iter_tiles()` exposes the stored tile grid directly, useful for
|
|
117
|
-
tile-based ML pipelines.
|
|
118
|
-
|
|
119
|
-
## Testing
|
|
120
|
-
|
|
121
|
-
Tests run against real TMAP samples when the `scce_external_center` data
|
|
122
|
-
directory (or `TAPSLIDE_TEST_DATA`) is present next to the repo, and are
|
|
123
|
-
skipped otherwise.
|
|
124
|
-
|
|
125
|
-
```bash
|
|
126
|
-
pip install -e .[dev]
|
|
127
|
-
pytest
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
## License
|
|
131
|
-
|
|
132
|
-
MIT — see [LICENSE](LICENSE).
|
|
133
|
-
|
|
134
|
-
## Acknowledgments
|
|
135
|
-
|
|
136
|
-
- [kfbslide](https://github.com/yifanfeng97/kfbslide) — the KFB reader this
|
|
137
|
-
project is modelled after.
|
|
138
|
-
- [OpenSlide](https://openslide.org/) — the API this library mimics.
|
|
139
|
-
- [ASlide](https://github.com/MrPeterJin/ASlide) — its independent
|
|
140
|
-
reverse-engineering of the TMAP06 layer/block structures (from decompiled
|
|
141
|
-
vendor SDK) was used to cross-validate this implementation. tmapslide is
|
|
142
|
-
an independent MIT-licensed implementation and ships no ASlide code.
|
tmapslide-0.2.0/README.md
DELETED
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
# TmapSlide
|
|
2
|
-
|
|
3
|
-

|
|
4
|
-
|
|
5
|
-
Pure Python reader for **UNIC TMAP** whole-slide images (WSI) with an
|
|
6
|
-
[OpenSlide](https://openslide.org/)-compatible API — the companion library to
|
|
7
|
-
[kfbslide](https://github.com/yifanfeng97/kfbslide) (KFB format).
|
|
8
|
-
|
|
9
|
-
```python
|
|
10
|
-
import tmapslide
|
|
11
|
-
|
|
12
|
-
slide = tmapslide.OpenSlide("sample.TMAP")
|
|
13
|
-
|
|
14
|
-
print(slide.dimensions) # (71424, 72704)
|
|
15
|
-
print(slide.level_count) # 10
|
|
16
|
-
print(slide.level_downsamples) # (1.0, 2.0, 4.0, ...)
|
|
17
|
-
|
|
18
|
-
region = slide.read_region((512, 512), 0, (1024, 1024)) # RGBA PIL image
|
|
19
|
-
thumb = slide.get_thumbnail((512, 512))
|
|
20
|
-
macro = slide.associated_images["macro"]
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
## Features
|
|
24
|
-
|
|
25
|
-
- **Pure Python** — no vendor SDK, no native dependencies; only Pillow.
|
|
26
|
-
- **OpenSlide-compatible API** — drop-in for code written against
|
|
27
|
-
`openslide`/`kfbslide`: `read_region`, `get_thumbnail`, `dimensions`,
|
|
28
|
-
`level_count`, `level_dimensions`, `level_downsamples`, `properties`,
|
|
29
|
-
`associated_images`.
|
|
30
|
-
- **Both known TMAP variants**: `TMAP06` (single pixel level, 612x512
|
|
31
|
-
overlapping tiles) and `TMAP07` (full pyramid, up to 10 levels, 256x256
|
|
32
|
-
tiles).
|
|
33
|
-
- **Fork-safe file handles** — safe to use from PyTorch `DataLoader` workers.
|
|
34
|
-
- LRU tile cache for fast repeated reads.
|
|
35
|
-
|
|
36
|
-
## Installation
|
|
37
|
-
|
|
38
|
-
```bash
|
|
39
|
-
pip install tmapslide
|
|
40
|
-
# or from source
|
|
41
|
-
pip install git+https://github.com/yifanfeng97/tmapslide.git
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
## Supported formats
|
|
45
|
-
|
|
46
|
-
| Format | Extension | Vendor | Backend |
|
|
47
|
-
| ------- | --------- | ------------------ | ----------- |
|
|
48
|
-
| TMAP 06 | `.TMAP` | UNIC (United Imaging) | Pure Python |
|
|
49
|
-
| TMAP 07 | `.TMAP` | UNIC (United Imaging) | Pure Python |
|
|
50
|
-
|
|
51
|
-
## API
|
|
52
|
-
|
|
53
|
-
### `tmapslide.OpenSlide(filename)`
|
|
54
|
-
|
|
55
|
-
| Member | Description |
|
|
56
|
-
| --------------------- | -------------------------------------------------- |
|
|
57
|
-
| `dimensions` | `(width, height)` at level 0 |
|
|
58
|
-
| `level_count` | number of pyramid levels |
|
|
59
|
-
| `level_dimensions` | `(w, h)` per level |
|
|
60
|
-
| `level_downsamples` | downsample factor per level |
|
|
61
|
-
| `properties` | read-only metadata mapping (`openslide.vendor=unic`, `tmap.*`) |
|
|
62
|
-
| `associated_images` | lazy mapping, typically `macro` / `label` |
|
|
63
|
-
| `read_region(loc, level, size)` | `PIL.Image` (RGBA) of the region |
|
|
64
|
-
| `get_thumbnail(size)` | thumbnail from the lowest resolution level |
|
|
65
|
-
| `get_best_level_for_downsample(ds)` | best level for a downsample factor |
|
|
66
|
-
| `close()` / context manager | release resources |
|
|
67
|
-
|
|
68
|
-
### `tmapslide.open_slide(filename)`
|
|
69
|
-
|
|
70
|
-
Alias of `OpenSlide(filename)`.
|
|
71
|
-
|
|
72
|
-
## Format notes (reverse-engineered)
|
|
73
|
-
|
|
74
|
-
TMAP is an undocumented proprietary format. This reader is built from
|
|
75
|
-
binary analysis of real scanner output, cross-validated against
|
|
76
|
-
[ASlide](https://github.com/MrPeterJin/ASlide). In short: both variants
|
|
77
|
-
store plain JPEG tiles with a small binary header and index tables; there
|
|
78
|
-
is no encryption.
|
|
79
|
-
|
|
80
|
-
- TMAP06 stores 3 pyramid levels (40x / 10x / 2.5x); TMAP07 stores up to
|
|
81
|
-
10 levels (40x down to 0.078x, halving each level).
|
|
82
|
-
- TMAP06 level-2 previews come from pre-rendered `ShrinkTile` entries and
|
|
83
|
-
are JPEG-compressed at that scale (slightly softer than downsampling
|
|
84
|
-
level 0 yourself).
|
|
85
|
-
- `iter_tiles()` exposes the stored tile grid directly, useful for
|
|
86
|
-
tile-based ML pipelines.
|
|
87
|
-
|
|
88
|
-
## Testing
|
|
89
|
-
|
|
90
|
-
Tests run against real TMAP samples when the `scce_external_center` data
|
|
91
|
-
directory (or `TAPSLIDE_TEST_DATA`) is present next to the repo, and are
|
|
92
|
-
skipped otherwise.
|
|
93
|
-
|
|
94
|
-
```bash
|
|
95
|
-
pip install -e .[dev]
|
|
96
|
-
pytest
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
## License
|
|
100
|
-
|
|
101
|
-
MIT — see [LICENSE](LICENSE).
|
|
102
|
-
|
|
103
|
-
## Acknowledgments
|
|
104
|
-
|
|
105
|
-
- [kfbslide](https://github.com/yifanfeng97/kfbslide) — the KFB reader this
|
|
106
|
-
project is modelled after.
|
|
107
|
-
- [OpenSlide](https://openslide.org/) — the API this library mimics.
|
|
108
|
-
- [ASlide](https://github.com/MrPeterJin/ASlide) — its independent
|
|
109
|
-
reverse-engineering of the TMAP06 layer/block structures (from decompiled
|
|
110
|
-
vendor SDK) was used to cross-validate this implementation. tmapslide is
|
|
111
|
-
an independent MIT-licensed implementation and ships no ASlide code.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|