imrw 0.1.0__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.
- imrw-0.1.0/PKG-INFO +5 -0
- imrw-0.1.0/README.md +20 -0
- imrw-0.1.0/pyproject.toml +14 -0
- imrw-0.1.0/setup.cfg +4 -0
- imrw-0.1.0/src/imrw/__init__.py +3 -0
- imrw-0.1.0/src/imrw/ops.py +33 -0
- imrw-0.1.0/src/imrw.egg-info/PKG-INFO +5 -0
- imrw-0.1.0/src/imrw.egg-info/SOURCES.txt +10 -0
- imrw-0.1.0/src/imrw.egg-info/dependency_links.txt +1 -0
- imrw-0.1.0/src/imrw.egg-info/requires.txt +2 -0
- imrw-0.1.0/src/imrw.egg-info/top_level.txt +1 -0
- imrw-0.1.0/tests/test_ops.py +65 -0
imrw-0.1.0/PKG-INFO
ADDED
imrw-0.1.0/README.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# imrw
|
|
2
|
+
Minimal image I/O library for Python.
|
|
3
|
+
|
|
4
|
+
## Features
|
|
5
|
+
- Read/Write images via `Pillow` & `numpy`.
|
|
6
|
+
- Clean and simple interface.
|
|
7
|
+
- `imread` always returns an RGB (`H x W x 3`) numpy array.
|
|
8
|
+
- `imwrite` expects a `uint8` array with shape `H x W`, `H x W x 1`, `H x W x 3`, or `H x W x 4`.
|
|
9
|
+
|
|
10
|
+
## Quick Start
|
|
11
|
+
```bash
|
|
12
|
+
pip install imrw
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
from imrw import imread, imwrite
|
|
17
|
+
|
|
18
|
+
image = imread("input.png")
|
|
19
|
+
imwrite("output.png", image)
|
|
20
|
+
```
|
imrw-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from PIL import Image
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def imread(path: str | Path) -> np.ndarray:
|
|
9
|
+
with Image.open(path) as img:
|
|
10
|
+
return np.array(img.convert("RGB"))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def imwrite(path: str | Path, img: np.ndarray, **kwargs: Any) -> None:
|
|
14
|
+
if img.dtype != np.uint8:
|
|
15
|
+
raise ValueError(
|
|
16
|
+
f"imwrite expects a uint8 numpy array, got dtype={img.dtype}"
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
if img.ndim not in (2, 3):
|
|
20
|
+
raise ValueError(
|
|
21
|
+
f"imwrite expects a 2D or 3D array, got shape={img.shape}"
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
if img.ndim == 3 and img.shape[2] not in (1, 3, 4):
|
|
25
|
+
raise ValueError(
|
|
26
|
+
"imwrite expects channel count in {1, 3, 4}, "
|
|
27
|
+
f"got shape={img.shape}"
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
if img.ndim == 3 and img.shape[2] == 1:
|
|
31
|
+
img = img[:, :, 0]
|
|
32
|
+
|
|
33
|
+
Image.fromarray(img).save(path, **kwargs)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
imrw
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pytest
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from imrw import imread, imwrite
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def test_im_ops_roundtrip(tmp_path: Path):
|
|
8
|
+
path = tmp_path / "test.png"
|
|
9
|
+
img = np.zeros((10, 10, 3), dtype=np.uint8)
|
|
10
|
+
img[0, 0] = [255, 0, 0]
|
|
11
|
+
|
|
12
|
+
imwrite(path, img)
|
|
13
|
+
assert path.exists()
|
|
14
|
+
|
|
15
|
+
loaded = imread(path)
|
|
16
|
+
assert np.array_equal(img, loaded)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_imwrite_rejects_non_uint8(tmp_path: Path):
|
|
20
|
+
path = tmp_path / "bad_dtype.png"
|
|
21
|
+
img = np.zeros((10, 10, 3), dtype=np.float32)
|
|
22
|
+
|
|
23
|
+
with pytest.raises(ValueError, match="uint8"):
|
|
24
|
+
imwrite(path, img)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_imwrite_rejects_invalid_ndim(tmp_path: Path):
|
|
28
|
+
path = tmp_path / "bad_ndim.png"
|
|
29
|
+
img = np.zeros((4, 4, 3, 1), dtype=np.uint8)
|
|
30
|
+
|
|
31
|
+
with pytest.raises(ValueError, match="2D or 3D"):
|
|
32
|
+
imwrite(path, img)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_imwrite_rejects_invalid_channel_count(tmp_path: Path):
|
|
36
|
+
path = tmp_path / "bad_channels.png"
|
|
37
|
+
img = np.zeros((10, 10, 2), dtype=np.uint8)
|
|
38
|
+
|
|
39
|
+
with pytest.raises(ValueError, match="channel count"):
|
|
40
|
+
imwrite(path, img)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def test_imwrite_grayscale_2d(tmp_path: Path):
|
|
44
|
+
path = tmp_path / "gray.png"
|
|
45
|
+
img = np.full((10, 10), 128, dtype=np.uint8)
|
|
46
|
+
|
|
47
|
+
imwrite(path, img)
|
|
48
|
+
assert path.exists()
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def test_imwrite_grayscale_3d(tmp_path: Path):
|
|
52
|
+
path = tmp_path / "gray3d.png"
|
|
53
|
+
img = np.full((10, 10, 1), 128, dtype=np.uint8)
|
|
54
|
+
|
|
55
|
+
imwrite(path, img)
|
|
56
|
+
assert path.exists()
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def test_imwrite_rgba(tmp_path: Path):
|
|
60
|
+
path = tmp_path / "rgba.png"
|
|
61
|
+
img = np.zeros((10, 10, 4), dtype=np.uint8)
|
|
62
|
+
img[..., 3] = 255
|
|
63
|
+
|
|
64
|
+
imwrite(path, img)
|
|
65
|
+
assert path.exists()
|