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 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}")
@@ -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