Metadata-Version: 2.4
Name: melu
Version: 0.8.0.post1
Summary: A content-bound image watermarking system
Author-email: "moxxie.knolastname" <moxxie@knolastna.me>
License-Expression: Apache-2.0
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: numpy>=1.24
Requires-Dist: scipy>=1.10
Requires-Dist: pillow>=11.3
Requires-Dist: PyWavelets>=1.4
Provides-Extra: gpu
Requires-Dist: torch>=2.1; extra == "gpu"
Provides-Extra: test
Requires-Dist: pytest>=7; extra == "test"
Dynamic: license-file

### Disclaimer!

This project is 100%, absolutely vibe-coded using Opus 5.5.

---

# melu

A content-bound image watermarking system.

```
pip install -e .            # or: pip install -e ".[gpu]" for the torch backend (CUDA)
melu embed  photo.png marked.png --salt "my secret"
melu detect marked.png --salt "my secret"        # exit 0 = found, 1 = not found
melu torture marked.png --salt "my secret" --crops
```

```python
import melu
marked, info = melu.embed("photo.png", salt="my secret")
marked.save("marked.png")                 # colour untouched, only luma carries the mark
r = melu.detect("marked.png", salt="my secret")
print(r.found, r.score, r.route)           # True 3.3 'A'
```

## Things to know

- **Keep the salt secret.** Anyone holding it can detect, and can attack the salt-only signals more easily.
- **Check `info.robust`** after embedding: tiny or flat images may not reach the threshold at the PSNR floor
  (the CLI refuses to write those unless `--force`).
- **GPU.** With torch and CUDA installed the searches run on the GPU automatically (about 2 s per marked
  image, 2–10 s for an unmarked one on an RTX 4060). `CBWM_GPU=0` forces numpy, `CBWM_WORKERS` sets FFT threads.

## Tests

`pip install -e ".[test]"` then `pytest`. The package must reproduce the lab code exactly on a synthetic image
(`tests/golden.json`), find marks after a quarter crop and a 3° rotation, and stay silent on unmarked images and
wrong salts.
