Metadata-Version: 2.4
Name: image-rs
Version: 0.2.1
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: POSIX :: Linux
Classifier: Topic :: Multimedia :: Graphics
Classifier: Topic :: Scientific/Engineering :: Image Processing
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Programming Language :: Python :: 3.15
Classifier: Programming Language :: Rust
Classifier: Programming Language :: Python :: Implementation :: CPython
Requires-Dist: numpy
License-File: LICENSE
Summary: A binding for the image crate from the Rust ecosystem.
Author: Ralph Schlett
License-Expression: MIT
Requires-Python: >=3.12
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
Project-URL: Repository, https://gitlab.com/aradar/image-rs

# image-rs

**Work in progress:** this project is not feature complete yet. The API may still change between releases.

**Not affiliated with the [image-rs](https://github.com/image-rs) organization on GitHub.** This is a third party
wrapper that makes the awesome Rust [image](https://crates.io/crates/image) crate available in the Python ecosystem.

image-rs is a thin wrapper around the image crate. It decodes images straight into NumPy arrays and encodes NumPy
arrays back into image bytes.

## Installation

```sh
pip install image-rs
```

or with [uv](https://docs.astral.sh/uv/):

```sh
uv add image-rs         # add it as a dependency to your project
uv pip install image-rs # or install it into the current environment
```

The package requires Python 3.12 or newer and depends on NumPy.

Prebuilt wheels are only available for **Linux x86_64** (manylinux2014, CPython 3.12–3.15). On every other platform
pip builds the package from the source distribution, which requires a [Rust toolchain](https://rustup.rs/) in version
1.85 or newer (the crate uses the Rust 2024 edition).

## Usage

```python
from pathlib import Path

import image_rs

# Decode a JPEG into a (height, width, 3) uint8 NumPy array.
image = image_rs.decode_uint8_image(
    Path("image.jpg").read_bytes(), image_rs.ImageFormat.JPEG, num_channels=3
)
print(image.shape, image.dtype)

# Encode it again as JPEG ...
jpeg_bytes = image_rs.encode_uint8_image(image, image_rs.EncodingOptions.Jpeg(quality=90))

# ... or as PNG with the fastest compression settings.
png_bytes = image_rs.encode_uint8_image(
    image,
    image_rs.EncodingOptions.Png(
        compression=image_rs.PngCompression.FAST,
        filter=image_rs.PngFilter.NO_FILTER,
    ),
)
Path("image.png").write_bytes(png_bytes)
```

Besides `decode_uint8_image` there are `decode_uint16_image` and `decode_float32_image` (values between 0 and 1) for
PNG images, and `encode_uint16_image` for 16 bit PNG encoding. The encoding functions release the GIL while they run.

For the full interface with docstrings see the type stub
[`image_rs.pyi`](https://gitlab.com/aradar/image-rs/-/blob/master/image_rs.pyi), which also ships with the package so
type checkers and IDEs pick it up. More examples live in the
[`examples`](https://gitlab.com/aradar/image-rs/-/tree/master/examples) directory.

## Currently supported functionality

- Image decoding
  - [x] PNG (uint8, uint16, float32)
  - [x] JPEG (uint8)
  - [ ] All other formats supported by the image crate
- Image encoding
  - [x] PNG (uint8, uint16)
  - [x] JPEG (uint8)
  - [ ] All other formats supported by the image crate

## Benchmark results

To run the [benchmark scripts](https://gitlab.com/aradar/image-rs/-/tree/master/examples) yourself, run
`steward sync --include-examples` after the [development setup](#development-setup) to get the needed dependencies.

*The presented results were measured on a PC with an AMD 9800x3d, using a build with the `dist` profile like the
wheels created by `steward build`. They were recorded with an earlier release on Python 3.10, before the minimum
supported version was raised to 3.12, so treat them as a rough comparison rather than exact numbers.*

### Decoding (benchmark.py)

```
3 channel uint8 JPEG decoding
--------------------------------------------------------------------------------
image_rs: time_per_pass=0.037s, total_time=3.656s, num_passes=100
OpenCV: time_per_pass=0.034s, total_time=3.440s, num_passes=100
torchvision: time_per_pass=0.031s, total_time=3.076s, num_passes=100
pywuffs: time_per_pass=0.059s, total_time=5.947s, num_passes=100
Pillow: time_per_pass=0.052s, total_time=5.165s, num_passes=100

3 channel uint8 PNG decoding
--------------------------------------------------------------------------------
image_rs: time_per_pass=0.056s, total_time=5.631s, num_passes=100
OpenCV: time_per_pass=0.096s, total_time=9.577s, num_passes=100
torchvision: time_per_pass=0.128s, total_time=12.760s, num_passes=100
pywuffs: time_per_pass=0.094s, total_time=9.424s, num_passes=100
Pillow: time_per_pass=0.113s, total_time=11.290s, num_passes=100

3 channel uint16 PNG decoding
--------------------------------------------------------------------------------
image_rs: time_per_pass=0.090s, total_time=8.975s, num_passes=100
OpenCV: time_per_pass=0.172s, total_time=17.218s, num_passes=100
torchvision: time_per_pass=0.212s, total_time=21.189s, num_passes=100
```

### Encoding (encoding_benchmark.py)

```
3 channel uint8 JPEG encoding (quality 90)
--------------------------------------------------------------------------------
image_rs: time_per_pass=0.177s, total_time=0.887s, num_passes=5, encoded_size=3660KiB
OpenCV: time_per_pass=0.022s, total_time=0.109s, num_passes=5, encoded_size=3427KiB
torchvision: time_per_pass=0.022s, total_time=0.108s, num_passes=5, encoded_size=3433KiB
Pillow: time_per_pass=0.027s, total_time=0.135s, num_passes=5, encoded_size=3433KiB

3 channel uint8 PNG encoding (balanced compression, zlib level 6)
--------------------------------------------------------------------------------
image_rs: time_per_pass=0.572s, total_time=2.858s, num_passes=5, encoded_size=15505KiB
OpenCV: time_per_pass=0.724s, total_time=3.621s, num_passes=5, encoded_size=15507KiB
torchvision: time_per_pass=2.129s, total_time=10.646s, num_passes=5, encoded_size=16266KiB
Pillow: time_per_pass=0.786s, total_time=3.931s, num_passes=5, encoded_size=15442KiB

3 channel uint8 PNG encoding (fastest compression, image_rs FAST vs. zlib level 1)
--------------------------------------------------------------------------------
image_rs: time_per_pass=0.053s, total_time=0.263s, num_passes=5, encoded_size=21709KiB
OpenCV: time_per_pass=0.282s, total_time=1.408s, num_passes=5, encoded_size=22495KiB
torchvision: time_per_pass=0.443s, total_time=2.216s, num_passes=5, encoded_size=16411KiB
Pillow: time_per_pass=0.346s, total_time=1.731s, num_passes=5, encoded_size=22354KiB

3 channel uint8 PNG encoding (fastest compression, image_rs FAST and no filter vs. zlib level 1)
--------------------------------------------------------------------------------
image_rs: time_per_pass=0.042s, total_time=0.210s, num_passes=5, encoded_size=36726KiB
OpenCV: time_per_pass=0.282s, total_time=1.408s, num_passes=5, encoded_size=22495KiB

3 channel uint16 PNG encoding (balanced compression, zlib level 6)
--------------------------------------------------------------------------------
image_rs: time_per_pass=1.433s, total_time=7.167s, num_passes=5, encoded_size=17304KiB
OpenCV: time_per_pass=1.692s, total_time=8.458s, num_passes=5, encoded_size=17327KiB

3 channel uint16 PNG encoding (fastest compression, image_rs FAST vs. zlib level 1)
--------------------------------------------------------------------------------
image_rs: time_per_pass=0.117s, total_time=0.586s, num_passes=5, encoded_size=42940KiB
OpenCV: time_per_pass=0.445s, total_time=2.225s, num_passes=5, encoded_size=31323KiB

3 channel uint16 PNG encoding (fastest compression, image_rs FAST and no filter vs. zlib level 1)
--------------------------------------------------------------------------------
image_rs: time_per_pass=0.098s, total_time=0.490s, num_passes=5, encoded_size=73449KiB
OpenCV: time_per_pass=0.443s, total_time=2.216s, num_passes=5, encoded_size=31323KiB
```


## Development setup

The project depends on the following tools that need to be present in the development environment: `git`, `cargo`, 
`rustc`, `rustfmt`, `clippy`, `docker` and `uv`.

Everything else will be installed with a `./steward.py sync`. After this first sync call the `steward` command is also 
everywhere available in the venv.

Other notable `steward` commands:

- `steward build local`: good for a quick local build of wheels. The wheels are bound to the glibc version of the
  local system, so use `steward build docker` for wheels that should run on other machines.
- `steward build docker`: by default builds wheels for all currently supported python targets in a manylinux2014 
  environment.
- `steward lint`: runs autoformatting and linting for the whole project.
- `steward prepare-publish`: removes `dist/`, builds all wheels and the sdist with `steward build docker` and checks
  them with `twine check`. The result in `dist/` is ready for the upload to PyPI.

The tests run with `uv run pytest`. The project is installed in editable mode, and `uv run` rebuilds the Rust
extension first whenever a file in `src/` changed.
