Metadata-Version: 2.4
Name: opennsfw-onnx
Version: 0.1.0
Summary: Yahoo open_nsfw NSFW image classifier on onnxruntime — a maintained, Apache-2.0 drop-in for opennsfw-standalone.
Project-URL: Homepage, https://github.com/gawryco/opennsfw-onnx
Project-URL: Repository, https://github.com/gawryco/opennsfw-onnx
Author: Gustavo Gawryszewski
License-Expression: Apache-2.0
License-File: LICENSE
License-File: NOTICE
License-File: THIRD_PARTY_LICENSES/open_nsfw-BSD-2-Clause.txt
License-File: THIRD_PARTY_LICENSES/opennsfw-standalone-MIT.txt
Keywords: content-moderation,image-classification,nsfw,onnx,onnxruntime,open_nsfw
Classifier: Development Status :: 4 - Beta
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering :: Image Recognition
Requires-Python: >=3.11
Requires-Dist: numpy>=1.24
Requires-Dist: onnxruntime>=1.17
Requires-Dist: pillow>=10.0
Provides-Extra: cli
Requires-Dist: typer>=0.12; extra == 'cli'
Provides-Extra: download
Requires-Dist: platformdirs>=4.0; extra == 'download'
Description-Content-Type: text/markdown

# opennsfw-onnx

Yahoo `open_nsfw` NSFW image classification, running on `onnxruntime` — no TensorFlow required.

[![License: Apache-2.0](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)
[![PyPI](https://img.shields.io/pypi/v/opennsfw-onnx.svg)](https://pypi.org/project/opennsfw-onnx/)
[![Python](https://img.shields.io/badge/python-3.11%2B-blue.svg)](https://www.python.org)

## What & why

`opennsfw-onnx` runs Yahoo's [`open_nsfw`](https://github.com/yahoo/open_nsfw) ResNet-50 model through `onnxruntime`, giving you a small, modern classifier for whether an image is safe-for-work with no TensorFlow dependency and no GPU required (though GPU/CoreML acceleration is supported).

It exists because the previously-maintained [`opennsfw-standalone`](https://pypi.org/project/opennsfw-standalone/) package has stalled — it pins ancient, CVE-affected dependencies (`pillow<9`, `numpy<2`) and hasn't been updated in years. `opennsfw-onnx` is a maintained, Apache-2.0 replacement that:

- under `CPUExecutionProvider`, matches `opennsfw-standalone`'s scores to within 1e-6 (observed 0.0 on the test fixtures) (see [Performance](#performance)),
- is a near drop-in migration — one import line changes,
- ships a modern API (`NSFWClassifier`, `Prediction`) plus a CLI,
- accepts paths, bytes, file-like objects, `PIL.Image`, and `numpy.ndarray` as input,
- supports batch inference and hardware acceleration (CUDA, TensorRT, DirectML, CoreML) via `onnxruntime` execution providers.

## Install

```bash
pip install opennsfw-onnx

# with the CLI (typer)
pip install "opennsfw-onnx[cli]"

# with runtime model download-and-cache support (platformdirs)
pip install "opennsfw-onnx[download]"
```

Or with `uv`:

```bash
uv add opennsfw-onnx
uv add "opennsfw-onnx[cli]"
```

The ONNX model weight (~22.5 MB) ships bundled inside the wheel by default, so no separate download is required for typical installs.

## Quickstart

```python
from opennsfw_onnx import NSFWClassifier

clf = NSFWClassifier()

# Any of these work: a path, raw bytes, a file-like object, a PIL.Image, or an ndarray.
pred = clf.classify("photo.jpg")

print(pred.nsfw, pred.sfw)      # P(nsfw), P(sfw) — floats that sum to ~1.0
print(pred.is_nsfw)             # bool, using the classifier's threshold (default 0.5)
print(float(pred))              # Prediction supports __float__ -> P(nsfw)

# Classify many images at once (preprocessing is parallelized across a thread pool).
preds = clf.classify_batch(["a.jpg", "b.jpg", "c.jpg"])

# One-off classification without managing a classifier instance.
from opennsfw_onnx import classify
pred = classify("photo.jpg")
```

`Prediction` is a frozen dataclass with fields `sfw: float`, `nsfw: float`, `threshold: float = 0.5`, a computed `is_nsfw: bool` property, and a `__float__` method returning `nsfw`.

## Drop-in migration from `opennsfw-standalone`

If you're using `opennsfw-standalone`, migrating is a one-line import change — `.load()` and `.infer(bytes) -> float` behave identically:

```python
# before
from opennsfw_standalone import OpenNSFWInferenceRunner

# after
from opennsfw_onnx.compat import OpenNSFWInferenceRunner

runner = OpenNSFWInferenceRunner.load()
score = runner.infer(image_bytes)   # float, P(nsfw)
```

| `opennsfw-standalone` | `opennsfw-onnx` |
|---|---|
| `from opennsfw_standalone import OpenNSFWInferenceRunner` | `from opennsfw_onnx.compat import OpenNSFWInferenceRunner` |
| `OpenNSFWInferenceRunner.load(model_path=None)` | `OpenNSFWInferenceRunner.load(model_path=None)` (identical signature) |
| `runner.infer(image_bytes: bytes) -> float` | `runner.infer(image_bytes: bytes) -> float` (identical signature) |
| TensorFlow backend | `onnxruntime` backend (CPU by default, accelerable) |
| pinned `pillow<9`, `numpy<2` | current `pillow`, `numpy` |

Everything else about your code stays the same. For new code, prefer the richer `NSFWClassifier` API described above.

## CLI

Install with the `cli` extra, then:

```bash
opennsfw-onnx classify photo.jpg
#     nsfw       sfw  verdict  path
#   0.0123    0.9877  sfw      photo.jpg

opennsfw-onnx classify "photos/*.jpg" --json
opennsfw-onnx classify photo.jpg --threshold 0.3
opennsfw-onnx classify photo.jpg --fast              # skip the JPEG round-trip for speed
opennsfw-onnx classify photo.jpg --provider CPUExecutionProvider
```

Flags:

| Flag | Description |
|---|---|
| `-t`, `--threshold FLOAT` | NSFW decision threshold (default `0.5`). |
| `-j`, `--json` | Emit a JSON array of `{path, nsfw, sfw, is_nsfw}` instead of a text table. |
| `--fast` | Skip the JPEG re-encode round-trip (`jpeg_reencode=False`) for faster, slightly less exact scores. |
| `--provider` | Restrict/override the onnxruntime execution provider(s); repeatable. |

Arguments accept file paths and shell-style globs (expanded internally, so quote globs to avoid premature shell expansion). Exit codes: `0` if no image is NSFW, `1` if any image is NSFW, `2` if no files matched.

## Performance

- **Provider selection**: by default, `NSFWClassifier` auto-selects the fastest available `onnxruntime` execution provider, preferring `CUDAExecutionProvider` → `TensorrtExecutionProvider` → `DmlExecutionProvider` → `CoreMLExecutionProvider` → `CPUExecutionProvider`, filtered to whatever's actually installed. Pass `providers=[...]` to pin a specific provider or ordering.
- **Fidelity vs. providers**: on `CPUExecutionProvider`, `opennsfw-onnx` matches the original `opennsfw-standalone` scores to within 1e-6 (observed 0.0 on the test fixtures, verified against committed reference scores). Accelerated providers (CoreML, CUDA) can differ by up to ~5e-4 due to backend math differences — negligible for a 0–1 score decided at a 0.5 threshold, but pin `providers=["CPUExecutionProvider"]` if you need to reproduce the legacy scores. See [`docs/performance.md`](docs/performance.md) for details.
- **`jpeg_reencode`** (default `True`): applies the reference JPEG round-trip during preprocessing for exact score reproduction. Set `jpeg_reencode=False` (or CLI `--fast`) to skip it — faster, with scores shifting by less than 1e-2.
- **Batch inference**: `classify_batch()` preprocesses images in parallel across a thread pool (`max_workers` configurable) and runs them through the model as a single stacked batch.
- **`warmup()`**: call `clf.warmup()` after constructing a classifier to run one dummy inference and avoid paying first-call initialization cost during real workloads.
- **Thread pinning**: pass `intra_op_num_threads=N` to the constructor to control onnxruntime's intra-op thread pool size.

See [`docs/performance.md`](docs/performance.md) for a runnable benchmark snippet.

## Model resolution

`NSFWClassifier` resolves the ONNX model file in this order, verifying its sha256 at every step (hard failure on mismatch):

1. an explicit `model_path` argument,
2. the `OPENNSFW_ONNX_MODEL_PATH` environment variable,
3. the model bundled inside the installed wheel,
4. as a last resort, downloading and caching it (requires the `download` extra).

See [`docs/api.md`](docs/api.md) for the full reference.

## Provenance & credits

- The model weights are Yahoo's [`open_nsfw`](https://github.com/yahoo/open_nsfw) (BSD-2-Clause, Copyright (c) 2016 Yahoo Inc.), converted to ONNX.
- The image preprocessing pipeline and the `opennsfw_onnx.compat` API are derived from [`opennsfw-standalone`](https://pypi.org/project/opennsfw-standalone/) (MIT, Copyright (c) 2017 Sector Labs).

Full license texts and lineage are in [`NOTICE`](NOTICE) and [`THIRD_PARTY_LICENSES/`](THIRD_PARTY_LICENSES/). See also [`docs/provenance.md`](docs/provenance.md) for the model's origin, extraction chain, and checksum.

## License

Apache-2.0. See [`LICENSE`](LICENSE).
