Metadata-Version: 2.4
Name: mrxsslide
Version: 0.1.0
Summary: Pure Python MRXS (3DHISTECH MIRAX) whole-slide image reader with OpenSlide-compatible API
Project-URL: Homepage, https://github.com/yifanfeng97/mrxsslide
Project-URL: Repository, https://github.com/yifanfeng97/mrxsslide
Project-URL: Issues, https://github.com/yifanfeng97/mrxsslide/issues
Author-email: Yifan Feng <evanfeng97@gmail.com>
License-Expression: MIT
License-File: LICENSE
Keywords: 3dhistech,digital-pathology,mirax,mrxs,openslide,pathology,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.1.0
Provides-Extra: batch
Requires-Dist: numpy>=1.24; extra == 'batch'
Provides-Extra: dev
Requires-Dist: mypy; extra == 'dev'
Requires-Dist: openslide-bin>=4.0.1.2; extra == 'dev'
Requires-Dist: openslide-python; extra == 'dev'
Requires-Dist: pytest-cov; extra == 'dev'
Requires-Dist: pytest>=7.0; extra == 'dev'
Requires-Dist: ruff; extra == 'dev'
Provides-Extra: jxr
Requires-Dist: imagecodecs>=2023.1.23; extra == 'jxr'
Description-Content-Type: text/markdown

<h1 align="center">MRXSSlide</h1>

<p align="center">
  <strong>纯 Python 实现的 MRXS（3DHISTECH MIRAX）数字病理切片读取库，提供与 OpenSlide 完全兼容的 API</strong>
</p>

<p align="center">
  <a href="README_EN.md">English</a> |
  <a href="README.md">简体中文</a>
</p>

<p align="center">
  <a href="https://pypi.org/project/mrxsslide/">
    <img src="https://img.shields.io/pypi/v/mrxsslide?style=for-the-badge&logo=pypi&logoColor=white&labelColor=1a1a2e&color=3776ab" alt="PyPI Version">
  </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&labelColor=1a1a2e" alt="License">
  </a>
  <a href="https://pypi.org/project/mrxsslide/">
    <img src="https://img.shields.io/pypi/dm/mrxsslide?style=for-the-badge&logo=pypi&logoColor=white&labelColor=1a1a2e&color=f97316" alt="Downloads">
  </a>
  <a href="https://github.com/yifanfeng97/mrxsslide/stargazers">
    <img src="https://img.shields.io/github/stars/yifanfeng97/mrxsslide?style=for-the-badge&logo=github&labelColor=1a1a2e&color=facc15" alt="GitHub Stars">
  </a>
</p>

<p align="center">
  <a href="#-特性">✨ 特性</a> •
  <a href="#-安装">📦 安装</a> •
  <a href="#-快速开始">🚀 快速开始</a> •
  <a href="#-api-参考">📖 API</a> •
  <a href="#-性能">⚡ 性能</a>
</p>

---

## ✨ 特性

- 🐍 **纯 Python 实现** — 仅依赖 Pillow，零原生依赖，跨平台开箱即用
- 🔄 **OpenSlide 兼容 API** — 可直接替换 `openslide-python`，无需修改业务代码
- ⚡ **并行解码 + LRU 帧缓存** — 多线程解码 JPEG/PNG 帧，重复读取显著加速
- 📁 **支持直接传数据目录** — `.mrxs` 主文件缺失时可直接打开同名数据目录
- 🔺 **金字塔多层级读取** — 自动解析 MRXS 全部缩放层级（含由底层帧派生的高层级）
- 🖼️ **关联图像读取** — 支持 macro、label、thumbnail
- 📊 **完整元数据支持** — MPP、扫描倍率、背景色及全部 INI 键（`mirax.GROUP.KEY`）
- 🧪 **实验性 JPEG-XR 支持** — `pip install mrxsslide[jxr]`（基于 imagecodecs）

---

## 📦 安装

### 使用 uv（推荐）

```bash
uv pip install mrxsslide
```

### 使用 pip

```bash
pip install mrxsslide
```

仅依赖 Pillow，任何平台都能直接安装。

### JPEG-XR 支持（实验性）

MRXS 切片也可能使用 JPEG-XR 压缩，需安装可选依赖：

```bash
pip install mrxsslide[jxr]
```

### 开发环境

```bash
uv sync --extra dev --extra batch
```

`dev` 含测试/ lint 依赖及 openslide 对比测试所需的 `openslide-python` +
`openslide-bin`（C 库）；`batch` 含 `read_regions_batch` 与
`tests/test_batch.py` 所需的 numpy。缺 `batch` 时批量测试会在收集期
ImportError，缺 `openslide-bin` 时 17 个 openslide 对比测试会被静默 skip。

---

## 🚀 快速开始

### 作为 OpenSlide 的 drop-in 替代品

```python
import mrxsslide as openslide

slide = openslide.OpenSlide("path/to/sample.mrxs")

print(f"层级数: {slide.level_count}")
print(f"Level 0 尺寸: {slide.dimensions}")
for i in range(slide.level_count):
    print(f"  Level {i}: {slide.level_dimensions[i]} "
          f"downsample={slide.level_downsamples[i]}")

# 读取区域（location 为 level 0 坐标，返回 RGBA）
img = slide.read_region((100000, 100000), 0, (512, 512))
img.save("region.png")

# 缩略图
thumb = slide.get_thumbnail((512, 512))
thumb.save("thumbnail.png")

# 关联图像
macro = slide.associated_images["macro"]
macro.save("macro.png")

# 属性读取
vendor = slide.properties[openslide.PROPERTY_NAME_VENDOR]
mpp_x = slide.properties[openslide.PROPERTY_NAME_MPP_X]

slide.close()
```

### 上下文管理器

```python
with openslide.OpenSlide("sample.mrxs") as slide:
    img = slide.read_region((0, 0), 0, (256, 256))
# 自动 close
```

### 批量读取

ML pipeline 按批量读 patch 时，`read_regions_batch` 把所有坐标命中的
缺失帧合并为一次并行解码，并直接返回 numpy ndarray。适用场景：解码/磁盘
开销占主导（冷页缓存、大帧、JPEG-XR）或下游需要 ndarray 形态；小帧 +
热页缓存场景下不优于逐次 `read_region`。
结果第 i 项与 `read_region(locs[i], level, size)` 逐像素一致。
需要可选依赖 numpy：`pip install mrxsslide[batch]`。

```python
locs = [(50000, 90000), (20000, 3000), (500, 60000)]
batch = slide.read_regions_batch(locs, 0, (512, 512))
print(batch.shape, batch.dtype)  # (3, 512, 512, 4) uint8

# mode="RGB"：透明区合成到 openslide.background-color 背景色上
rgb = slide.read_regions_batch(locs, 0, (512, 512), mode="RGB")
print(rgb.shape)  # (3, 512, 512, 3)
```

### 直接打开数据目录

`.mrxs` 主文件丢失或仅拷贝了数据目录时，可直接传入目录路径：

```python
slide = openslide.OpenSlide("path/to/sample")  # 与 sample.mrxs 同名的数据目录
```

### 命令行示例

```bash
python examples/read_region.py sample.mrxs 100000 100000 0 512 512
```

---

## 📖 API 参考

### `OpenSlide(filename, max_workers=0)`

打开一个 MRXS 切片（`.mrxs` 文件或同名数据目录）。
`max_workers=0` 表示自动选择解码线程数（最多 16）。

### 类方法

| 方法 | 说明 |
|------|------|
| `OpenSlide.detect_format(filename)` | 检测文件格式，返回 `"mirax"` 或 `None` |

### 属性

| 属性 | 类型 | 说明 |
|------|------|------|
| `level_count` | `int` | 金字塔层级数 |
| `dimensions` | `(int, int)` | Level 0 尺寸（最高分辨率） |
| `level_dimensions` | `Tuple[(w, h), ...]` | 每层尺寸 |
| `level_downsamples` | `Tuple[float, ...]` | 每层下采样倍数 |
| `properties` | `Mapping[str, str]` | 元数据属性（只读映射） |
| `associated_images` | `Mapping[str, PIL.Image]` | 关联图像：macro、label、thumbnail |
| `color_profile` | `object \| None` | ICC 颜色配置文件（当前返回 `None`） |

### 方法

| 方法 | 说明 |
|------|------|
| `read_region(location, level, size)` | 读取指定区域，返回 **RGBA** 图像 |
| `read_regions_batch(locations, level, size, mode="RGBA")` | 批量读取，返回 numpy `(N, h, w, C)` uint8（需 `mrxsslide[batch]`） |
| `get_best_level_for_downsample(downsample)` | 根据下采样倍数选择最佳层级 |
| `get_thumbnail(size)` | 生成缩略图（RGB，LANCZOS 重采样） |
| `set_cache(cache)` | API 兼容方法（当前为 no-op） |
| `close()` | 关闭并释放资源 |

### 属性常量

```python
from mrxsslide import (
    PROPERTY_NAME_VENDOR,           # "openslide.vendor"
    PROPERTY_NAME_MPP_X,            # "openslide.mpp-x"
    PROPERTY_NAME_MPP_Y,            # "openslide.mpp-y"
    PROPERTY_NAME_OBJECTIVE_POWER,  # "openslide.objective-power"
    PROPERTY_NAME_BACKGROUND_COLOR, # "openslide.background-color"
    PROPERTY_NAME_BOUNDS_X,         # "openslide.bounds-x"
    PROPERTY_NAME_BOUNDS_Y,         # "openslide.bounds-y"
    PROPERTY_NAME_BOUNDS_WIDTH,     # "openslide.bounds-width"
    PROPERTY_NAME_BOUNDS_HEIGHT,    # "openslide.bounds-height"
    PROPERTY_NAME_QUICKHASH1,       # "openslide.quickhash-1"
)
```

### 异常

`OpenSlideError`、`OpenSlideUnsupportedFormatError`（与 openslide-python 同名），
以及对应别名 `MrxsError`、`MrxsOpenError`、`MrxsUnsupportedFormatError`。

---

## ⚡ 性能

与 OpenSlide（C 实现）读取**同一** MRXS 文件对比（测试脚本见
`benchmarks/compare_mrxs_openslide.py`）：

| 场景 | mrxsslide | OpenSlide | 加速比 |
|------|-----------|-----------|--------|
| 随机读取 50 × 512×512（level 0） | 0.02 s | 0.10 s | **6.2×** |
| 同 50 区域第二遍（LRU 帧缓存热） | 0.02 s | 0.12 s | **6.6×** |
| 重复读取同一区域 ×50（LRU 缓存） | 0.01 s | 0.03 s | **2.2×** |
| 顺序扫描 400 × 256×256（level 4） | 1.5 s | 1.6 s | 1.2× |

> 测试文件：`1053891-15 pou2F3.mrxs`（83,379 × 185,672，9 层，JPEG 压缩）。  
> 环境：Intel Xeon E5-2678 v3 / Python 3.11 / Pillow 12.3.0 / openslide-python 1.4.6（OpenSlide 4.0.1），测试时机器负载较高。  
> 方法：每个场景 mrxsslide 与 OpenSlide 交替运行 3 次取中位数，共 4 轮取代表值，保证两者面对相同的页缓存热度；该口径下页缓存已被预热，不反映页缓存完全冷的首次读取。  
> 说明：随机与重复读取场景 mrxsslide 明显更快（并行解码 + LRU 帧缓存）；顺序扫描场景基本持平（4 轮加速比 0.95–1.21×）。不同样本、压缩格式与硬件会导致差异。

---

## 🏗️ 架构

MRXSSlide 完全基于纯 Python 实现，通过直接解析 MRXS 格式完成图像读取：

- **无需任何 C/C++ 扩展或系统动态库**
- **不依赖 OpenSlide、libjpeg、openjpeg 等外部库**
- `read_region` 三阶段流水线：收集命中 tile → 线程池并行解码帧 → 仿射对齐 + alpha 合成
- 每个数据文件共享只读 fd + `os.pread`，无线程锁
- **适合服务器、容器等不便安装原生依赖的场景**

---

## 📁 项目结构

```
mrxsslide/
├── src/mrxsslide/
│   ├── __init__.py          # 包入口，导出 OpenSlide API
│   ├── _slide.py            # OpenSlide 主类（三阶段 read_region）
│   ├── _mrxsformat.py       # MRXS / Slidedat / 索引页解析
│   ├── _codecs.py           # JPEG / PNG / JPEG-XR 解码
│   ├── _cache.py            # LRU 帧缓存
│   └── _exceptions.py       # OpenSlideError / 兼容异常
├── tests/                   # 测试（含 sample.mrxs 软链）
├── examples/                # 示例脚本
├── benchmarks/              # 与 OpenSlide 的对比基准
├── scripts/                 # 冒烟工具
├── README.md
├── LICENSE
└── pyproject.toml
```

---

## ⚠️ 已知限制

1. **只读**：目前不支持写入 MRXS 文件。
2. **JPEG-XR 为实验性**：代码路径就绪但缺少真实样本验证。
3. **首次冷读未覆盖**：基准为页缓存预热后的交替 median-of-3 口径，该口径下 mrxsslide 各场景均不慢于 OpenSlide；页缓存完全冷的首次读取对比暂无数据（详见[性能](#-性能)）。

---

## 📄 License

[MIT](LICENSE)

Copyright (c) 2026 Yifan Feng
