mrxsslide 0.1.0__py3-none-any.whl
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.
- mrxsslide/__init__.py +60 -0
- mrxsslide/_cache.py +40 -0
- mrxsslide/_codecs.py +37 -0
- mrxsslide/_exceptions.py +22 -0
- mrxsslide/_mrxsformat.py +647 -0
- mrxsslide/_slide.py +551 -0
- mrxsslide-0.1.0.dist-info/METADATA +328 -0
- mrxsslide-0.1.0.dist-info/RECORD +10 -0
- mrxsslide-0.1.0.dist-info/WHEEL +4 -0
- mrxsslide-0.1.0.dist-info/licenses/LICENSE +21 -0
mrxsslide/__init__.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MRXSSlide — Pure Python MRXS (3DHISTECH MIRAX) whole-slide image reader.
|
|
3
|
+
|
|
4
|
+
A minimal, cross-platform reader for MRXS whole-slide images
|
|
5
|
+
with an OpenSlide-compatible API.
|
|
6
|
+
|
|
7
|
+
Author: Yifan Feng <evanfeng97@gmail.com>
|
|
8
|
+
License: MIT
|
|
9
|
+
|
|
10
|
+
Drop-in replacement usage:
|
|
11
|
+
import mrxsslide as openslide
|
|
12
|
+
slide = openslide.OpenSlide("sample.mrxs")
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from ._exceptions import (
|
|
16
|
+
MrxsError,
|
|
17
|
+
MrxsOpenError,
|
|
18
|
+
MrxsUnsupportedFormatError,
|
|
19
|
+
OpenSlideError,
|
|
20
|
+
OpenSlideUnsupportedFormatError,
|
|
21
|
+
)
|
|
22
|
+
from ._slide import MrxsSlide, OpenSlide, open_slide
|
|
23
|
+
|
|
24
|
+
__version__ = "0.1.0"
|
|
25
|
+
|
|
26
|
+
# Standard OpenSlide property name constants
|
|
27
|
+
PROPERTY_NAME_VENDOR = "openslide.vendor"
|
|
28
|
+
PROPERTY_NAME_QUICKHASH1 = "openslide.quickhash-1"
|
|
29
|
+
PROPERTY_NAME_BACKGROUND_COLOR = "openslide.background-color"
|
|
30
|
+
PROPERTY_NAME_OBJECTIVE_POWER = "openslide.objective-power"
|
|
31
|
+
PROPERTY_NAME_MPP_X = "openslide.mpp-x"
|
|
32
|
+
PROPERTY_NAME_MPP_Y = "openslide.mpp-y"
|
|
33
|
+
PROPERTY_NAME_BOUNDS_X = "openslide.bounds-x"
|
|
34
|
+
PROPERTY_NAME_BOUNDS_Y = "openslide.bounds-y"
|
|
35
|
+
PROPERTY_NAME_BOUNDS_WIDTH = "openslide.bounds-width"
|
|
36
|
+
PROPERTY_NAME_BOUNDS_HEIGHT = "openslide.bounds-height"
|
|
37
|
+
|
|
38
|
+
__all__ = [
|
|
39
|
+
# Primary OpenSlide API
|
|
40
|
+
"OpenSlide",
|
|
41
|
+
"OpenSlideError",
|
|
42
|
+
"OpenSlideUnsupportedFormatError",
|
|
43
|
+
# Property constants
|
|
44
|
+
"PROPERTY_NAME_VENDOR",
|
|
45
|
+
"PROPERTY_NAME_QUICKHASH1",
|
|
46
|
+
"PROPERTY_NAME_BACKGROUND_COLOR",
|
|
47
|
+
"PROPERTY_NAME_OBJECTIVE_POWER",
|
|
48
|
+
"PROPERTY_NAME_MPP_X",
|
|
49
|
+
"PROPERTY_NAME_MPP_Y",
|
|
50
|
+
"PROPERTY_NAME_BOUNDS_X",
|
|
51
|
+
"PROPERTY_NAME_BOUNDS_Y",
|
|
52
|
+
"PROPERTY_NAME_BOUNDS_WIDTH",
|
|
53
|
+
"PROPERTY_NAME_BOUNDS_HEIGHT",
|
|
54
|
+
# Aliases
|
|
55
|
+
"MrxsSlide",
|
|
56
|
+
"open_slide",
|
|
57
|
+
"MrxsError",
|
|
58
|
+
"MrxsUnsupportedFormatError",
|
|
59
|
+
"MrxsOpenError",
|
|
60
|
+
]
|
mrxsslide/_cache.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""LRU cache for decoded MRXS camera images.
|
|
2
|
+
|
|
3
|
+
Author: Yifan Feng <evanfeng97@gmail.com>
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from collections import OrderedDict
|
|
7
|
+
from typing import Hashable, Optional
|
|
8
|
+
|
|
9
|
+
from PIL import Image
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class _LRUCache:
|
|
13
|
+
"""OrderedDict-backed LRU cache for decoded images (O(1) ops)."""
|
|
14
|
+
|
|
15
|
+
__slots__ = ("capacity", "_cache")
|
|
16
|
+
|
|
17
|
+
def __init__(self, capacity: int):
|
|
18
|
+
self.capacity = max(0, capacity)
|
|
19
|
+
self._cache: OrderedDict[Hashable, Image.Image] = OrderedDict()
|
|
20
|
+
|
|
21
|
+
def get(self, key: Hashable) -> Optional[Image.Image]:
|
|
22
|
+
if key not in self._cache:
|
|
23
|
+
return None
|
|
24
|
+
self._cache.move_to_end(key)
|
|
25
|
+
return self._cache[key]
|
|
26
|
+
|
|
27
|
+
def put(self, key: Hashable, value: Image.Image) -> None:
|
|
28
|
+
if self.capacity <= 0:
|
|
29
|
+
return
|
|
30
|
+
if key in self._cache:
|
|
31
|
+
self._cache.move_to_end(key)
|
|
32
|
+
elif len(self._cache) >= self.capacity:
|
|
33
|
+
self._cache.popitem(last=False)
|
|
34
|
+
self._cache[key] = value
|
|
35
|
+
|
|
36
|
+
def clear(self) -> None:
|
|
37
|
+
self._cache.clear()
|
|
38
|
+
|
|
39
|
+
def __len__(self) -> int:
|
|
40
|
+
return len(self._cache)
|
mrxsslide/_codecs.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""按 IMAGE_FORMAT 分发的图像解码器。
|
|
2
|
+
|
|
3
|
+
JPEG/PNG/BMP24 走 Pillow;JPEG-XR 走 imagecodecs(可选依赖,experimental:
|
|
4
|
+
当前无 JPEG-XR 样本验证,通道顺序等待真实数据确认)。
|
|
5
|
+
|
|
6
|
+
Author: Yifan Feng <evanfeng97@gmail.com>
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import io
|
|
10
|
+
|
|
11
|
+
from PIL import Image
|
|
12
|
+
|
|
13
|
+
from ._exceptions import OpenSlideUnsupportedFormatError
|
|
14
|
+
|
|
15
|
+
_PILLOW_FORMATS = {"JPEG", "PNG", "BMP24"}
|
|
16
|
+
_JXR_FORMATS = {"JPEGXR", "JXR", "JPEG-XR"}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def decode_image(data: bytes, image_format: str) -> Image.Image:
|
|
20
|
+
"""解码一帧图像数据,返回 RGB 模式的 PIL Image。"""
|
|
21
|
+
fmt = image_format.upper()
|
|
22
|
+
if fmt in _PILLOW_FORMATS:
|
|
23
|
+
img: Image.Image = Image.open(io.BytesIO(data))
|
|
24
|
+
img.load()
|
|
25
|
+
if img.mode != "RGB":
|
|
26
|
+
img = img.convert("RGB")
|
|
27
|
+
return img
|
|
28
|
+
if fmt in _JXR_FORMATS:
|
|
29
|
+
try:
|
|
30
|
+
import imagecodecs # type: ignore[import-not-found]
|
|
31
|
+
except ImportError:
|
|
32
|
+
raise OpenSlideUnsupportedFormatError(
|
|
33
|
+
"JPEG-XR compressed slide requires imagecodecs: "
|
|
34
|
+
"pip install mrxsslide[jxr]"
|
|
35
|
+
) from None
|
|
36
|
+
return Image.fromarray(imagecodecs.jxr_decode(data)).convert("RGB")
|
|
37
|
+
raise OpenSlideUnsupportedFormatError(f"Unrecognized image format: {image_format}")
|
mrxsslide/_exceptions.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""MRXSSlide / OpenSlide-compatible exceptions.
|
|
2
|
+
|
|
3
|
+
Author: Yifan Feng <evanfeng97@gmail.com>
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class OpenSlideError(Exception):
|
|
8
|
+
"""Base exception for OpenSlide errors."""
|
|
9
|
+
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class OpenSlideUnsupportedFormatError(OpenSlideError):
|
|
14
|
+
"""File format not supported or file is corrupted."""
|
|
15
|
+
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# Backward compatibility aliases
|
|
20
|
+
MrxsError = OpenSlideError
|
|
21
|
+
MrxsUnsupportedFormatError = OpenSlideUnsupportedFormatError
|
|
22
|
+
MrxsOpenError = OpenSlideError
|