perceptimg 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.
- perceptimg-0.1.0/LICENSE +21 -0
- perceptimg-0.1.0/MANIFEST.in +5 -0
- perceptimg-0.1.0/PKG-INFO +478 -0
- perceptimg-0.1.0/README.md +434 -0
- perceptimg-0.1.0/examples/basic_usage.py +27 -0
- perceptimg-0.1.0/perceptimg/__init__.py +84 -0
- perceptimg-0.1.0/perceptimg/__main__.py +6 -0
- perceptimg-0.1.0/perceptimg/adapters/__init__.py +19 -0
- perceptimg-0.1.0/perceptimg/adapters/pil_adapter.py +221 -0
- perceptimg-0.1.0/perceptimg/cli.py +374 -0
- perceptimg-0.1.0/perceptimg/core/__init__.py +0 -0
- perceptimg-0.1.0/perceptimg/core/analyzer.py +113 -0
- perceptimg-0.1.0/perceptimg/core/batch/__init__.py +528 -0
- perceptimg-0.1.0/perceptimg/core/batch/cache.py +93 -0
- perceptimg-0.1.0/perceptimg/core/batch/config.py +90 -0
- perceptimg-0.1.0/perceptimg/core/batch/processor.py +144 -0
- perceptimg-0.1.0/perceptimg/core/batch.py +54 -0
- perceptimg-0.1.0/perceptimg/core/checkpoint.py +341 -0
- perceptimg-0.1.0/perceptimg/core/distributed.py +394 -0
- perceptimg-0.1.0/perceptimg/core/interfaces.py +126 -0
- perceptimg-0.1.0/perceptimg/core/metrics.py +205 -0
- perceptimg-0.1.0/perceptimg/core/metrics_exporter.py +303 -0
- perceptimg-0.1.0/perceptimg/core/optimizer.py +316 -0
- perceptimg-0.1.0/perceptimg/core/policy.py +187 -0
- perceptimg-0.1.0/perceptimg/core/rate_limiter.py +181 -0
- perceptimg-0.1.0/perceptimg/core/report.py +71 -0
- perceptimg-0.1.0/perceptimg/core/retry.py +244 -0
- perceptimg-0.1.0/perceptimg/core/strategy.py +157 -0
- perceptimg-0.1.0/perceptimg/engines/__init__.py +0 -0
- perceptimg-0.1.0/perceptimg/engines/apng_engine.py +41 -0
- perceptimg-0.1.0/perceptimg/engines/avif_engine.py +47 -0
- perceptimg-0.1.0/perceptimg/engines/base.py +42 -0
- perceptimg-0.1.0/perceptimg/engines/heif_engine.py +49 -0
- perceptimg-0.1.0/perceptimg/engines/jxl_engine.py +46 -0
- perceptimg-0.1.0/perceptimg/engines/pillow_engine.py +50 -0
- perceptimg-0.1.0/perceptimg/engines/webp_engine.py +49 -0
- perceptimg-0.1.0/perceptimg/exceptions.py +23 -0
- perceptimg-0.1.0/perceptimg/formats/__init__.py +16 -0
- perceptimg-0.1.0/perceptimg/formats/apng.py +18 -0
- perceptimg-0.1.0/perceptimg/formats/avif.py +22 -0
- perceptimg-0.1.0/perceptimg/formats/gif.py +18 -0
- perceptimg-0.1.0/perceptimg/formats/heif.py +27 -0
- perceptimg-0.1.0/perceptimg/formats/jpeg.py +35 -0
- perceptimg-0.1.0/perceptimg/formats/jxl.py +27 -0
- perceptimg-0.1.0/perceptimg/formats/png.py +20 -0
- perceptimg-0.1.0/perceptimg/formats/tiff.py +21 -0
- perceptimg-0.1.0/perceptimg/formats/webp.py +22 -0
- perceptimg-0.1.0/perceptimg/perceptimg/__main__.py +8 -0
- perceptimg-0.1.0/perceptimg/tests/test_cli_defaults.py +37 -0
- perceptimg-0.1.0/perceptimg/tests/test_cli_policy_flags.py +24 -0
- perceptimg-0.1.0/perceptimg/tests/test_engine_fallback.py +38 -0
- perceptimg-0.1.0/perceptimg/tests/test_engine_priority.py +37 -0
- perceptimg-0.1.0/perceptimg/tests/test_engine_priority_selection.py +53 -0
- perceptimg-0.1.0/perceptimg/tests/test_optimize_bytes_original.py +15 -0
- perceptimg-0.1.0/perceptimg/tests/test_optimize_from_analysis.py +15 -0
- perceptimg-0.1.0/perceptimg/tests/test_optimizer_memory.py +26 -0
- perceptimg-0.1.0/perceptimg/utils/__init__.py +0 -0
- perceptimg-0.1.0/perceptimg/utils/heuristics.py +166 -0
- perceptimg-0.1.0/perceptimg/utils/image_io.py +130 -0
- perceptimg-0.1.0/perceptimg/utils/logging_config.py +61 -0
- perceptimg-0.1.0/perceptimg/utils/validation.py +36 -0
- perceptimg-0.1.0/perceptimg.egg-info/PKG-INFO +478 -0
- perceptimg-0.1.0/perceptimg.egg-info/SOURCES.txt +106 -0
- perceptimg-0.1.0/perceptimg.egg-info/dependency_links.txt +1 -0
- perceptimg-0.1.0/perceptimg.egg-info/entry_points.txt +2 -0
- perceptimg-0.1.0/perceptimg.egg-info/requires.txt +14 -0
- perceptimg-0.1.0/perceptimg.egg-info/top_level.txt +1 -0
- perceptimg-0.1.0/pyproject.toml +118 -0
- perceptimg-0.1.0/setup.cfg +4 -0
- perceptimg-0.1.0/tests/__init__.py +0 -0
- perceptimg-0.1.0/tests/test_additional_coverage.py +153 -0
- perceptimg-0.1.0/tests/test_analyzer.py +19 -0
- perceptimg-0.1.0/tests/test_batch.py +255 -0
- perceptimg-0.1.0/tests/test_cli_coverage.py +47 -0
- perceptimg-0.1.0/tests/test_cli_default_outputs.py +22 -0
- perceptimg-0.1.0/tests/test_cli_main.py +70 -0
- perceptimg-0.1.0/tests/test_cli_main_default.py +22 -0
- perceptimg-0.1.0/tests/test_cli_main_default_out.py +22 -0
- perceptimg-0.1.0/tests/test_cli_main_entrypoint.py +42 -0
- perceptimg-0.1.0/tests/test_engines.py +73 -0
- perceptimg-0.1.0/tests/test_engines_extended.py +225 -0
- perceptimg-0.1.0/tests/test_engines_success.py +53 -0
- perceptimg-0.1.0/tests/test_enterprise.py +270 -0
- perceptimg-0.1.0/tests/test_heuristics_edge_case.py +12 -0
- perceptimg-0.1.0/tests/test_heuristics_line56.py +11 -0
- perceptimg-0.1.0/tests/test_image_io_errors.py +100 -0
- perceptimg-0.1.0/tests/test_logging_and_validation.py +14 -0
- perceptimg-0.1.0/tests/test_logging_formatter.py +32 -0
- perceptimg-0.1.0/tests/test_metrics.py +24 -0
- perceptimg-0.1.0/tests/test_metrics_perceptual_score.py +15 -0
- perceptimg-0.1.0/tests/test_metrics_perceptual_score_zero.py +13 -0
- perceptimg-0.1.0/tests/test_metrics_score.py +13 -0
- perceptimg-0.1.0/tests/test_optimizer.py +65 -0
- perceptimg-0.1.0/tests/test_optimizer_branches.py +64 -0
- perceptimg-0.1.0/tests/test_optimizer_branches_more.py +82 -0
- perceptimg-0.1.0/tests/test_optimizer_errors.py +40 -0
- perceptimg-0.1.0/tests/test_optimizer_no_candidates.py +23 -0
- perceptimg-0.1.0/tests/test_optimizer_no_candidates_path.py +29 -0
- perceptimg-0.1.0/tests/test_optimizer_policy_accepts.py +27 -0
- perceptimg-0.1.0/tests/test_optimizer_policy_reject.py +29 -0
- perceptimg-0.1.0/tests/test_policy.py +62 -0
- perceptimg-0.1.0/tests/test_policy_edge_cases.py +134 -0
- perceptimg-0.1.0/tests/test_policy_from_json_error.py +8 -0
- perceptimg-0.1.0/tests/test_strategy_edge.py +28 -0
- perceptimg-0.1.0/tests/test_strategy_policy_combinations.py +17 -0
- perceptimg-0.1.0/tests/test_utils_and_formats.py +98 -0
- perceptimg-0.1.0/tests/test_utils_edges.py +41 -0
- perceptimg-0.1.0/tests/test_validation_edges.py +10 -0
perceptimg-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Marc Rivero Lopez
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: perceptimg
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Perceptual, policy-driven image optimization engine for modern workflows
|
|
5
|
+
Author-email: Marc Rivero Lopez <mriverolopez@gmail.com>
|
|
6
|
+
Maintainer-email: Marc Rivero Lopez <mriverolopez@gmail.com>
|
|
7
|
+
License: MIT
|
|
8
|
+
Project-URL: Homepage, https://github.com/seifreed/perceptimg
|
|
9
|
+
Project-URL: Documentation, https://github.com/seifreed/perceptimg#readme
|
|
10
|
+
Project-URL: Repository, https://github.com/seifreed/perceptimg
|
|
11
|
+
Project-URL: Issues, https://github.com/seifreed/perceptimg/issues
|
|
12
|
+
Project-URL: Changelog, https://github.com/seifreed/perceptimg/releases
|
|
13
|
+
Project-URL: Funding, https://buymeacoffee.com/seifreed
|
|
14
|
+
Keywords: image,optimization,compression,perceptual,ssim,psnr,webp,avif,jpeg-xl,heif,batch-processing,policy-driven
|
|
15
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
16
|
+
Classifier: Environment :: Console
|
|
17
|
+
Classifier: Intended Audience :: Developers
|
|
18
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
19
|
+
Classifier: Operating System :: OS Independent
|
|
20
|
+
Classifier: Programming Language :: Python :: 3
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
23
|
+
Classifier: Topic :: Multimedia :: Graphics
|
|
24
|
+
Classifier: Topic :: Multimedia :: Graphics :: Graphics Conversion
|
|
25
|
+
Classifier: Topic :: Scientific/Engineering :: Image Processing
|
|
26
|
+
Classifier: Typing :: Typed
|
|
27
|
+
Classifier: Framework :: AsyncIO
|
|
28
|
+
Requires-Python: >=3.13
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
License-File: LICENSE
|
|
31
|
+
Requires-Dist: pillow>=10.0.0
|
|
32
|
+
Requires-Dist: numpy>=1.26.0
|
|
33
|
+
Requires-Dist: scikit-image>=0.22.0
|
|
34
|
+
Provides-Extra: dev
|
|
35
|
+
Requires-Dist: pytest>=7.4; extra == "dev"
|
|
36
|
+
Requires-Dist: pytest-cov>=4.1; extra == "dev"
|
|
37
|
+
Requires-Dist: ruff>=0.4; extra == "dev"
|
|
38
|
+
Requires-Dist: black>=24.0; extra == "dev"
|
|
39
|
+
Requires-Dist: mypy>=1.8; extra == "dev"
|
|
40
|
+
Requires-Dist: bandit>=1.7; extra == "dev"
|
|
41
|
+
Provides-Extra: redis
|
|
42
|
+
Requires-Dist: redis>=4.5.0; extra == "redis"
|
|
43
|
+
Dynamic: license-file
|
|
44
|
+
|
|
45
|
+
<p align="center">
|
|
46
|
+
<img src="https://img.shields.io/badge/perceptimg-Image%20Optimization-blue?style=for-the-badge" alt="perceptimg">
|
|
47
|
+
</p>
|
|
48
|
+
|
|
49
|
+
<h1 align="center">perceptimg</h1>
|
|
50
|
+
|
|
51
|
+
<p align="center">
|
|
52
|
+
<strong>Perceptual, policy-driven image optimization for modern workflows</strong>
|
|
53
|
+
</p>
|
|
54
|
+
|
|
55
|
+
<p align="center">
|
|
56
|
+
<a href="https://pypi.org/project/perceptimg/"><img src="https://img.shields.io/pypi/v/perceptimg?style=flat-square&logo=pypi&logoColor=white" alt="PyPI Version"></a>
|
|
57
|
+
<a href="https://pypi.org/project/perceptimg/"><img src="https://img.shields.io/pypi/pyversions/perceptimg?style=flat-square&logo=python&logoColor=white" alt="Python Versions"></a>
|
|
58
|
+
<a href="https://github.com/seifreed/perceptimg/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-green?style=flat-square" alt="License"></a>
|
|
59
|
+
<a href="https://github.com/seifreed/perceptimg/actions"><img src="https://img.shields.io/github/actions/workflow/status/seifreed/perceptimg/ci.yml?style=flat-square&logo=github&label=CI" alt="CI Status"></a>
|
|
60
|
+
<a href="https://codecov.io/gh/seifreed/perceptimg"><img src="https://codecov.io/gh/seifreed/perceptimg/branch/main/graph/badge.svg?style=flat-square" alt="Coverage"></a>
|
|
61
|
+
</p>
|
|
62
|
+
|
|
63
|
+
<p align="center">
|
|
64
|
+
<a href="https://github.com/seifreed/perceptimg/stargazers"><img src="https://img.shields.io/github/stars/seifreed/perceptimg?style=flat-square" alt="GitHub Stars"></a>
|
|
65
|
+
<a href="https://github.com/seifreed/perceptimg/issues"><img src="https://img.shields.io/github/issues/seifreed/perceptimg?style=flat-square" alt="GitHub Issues"></a>
|
|
66
|
+
<a href="https://buymeacoffee.com/seifreed"><img src="https://img.shields.io/badge/Buy%20Me%20a%20Coffee-support-yellow?style=flat-square&logo=buy-me-a-coffee&logoColor=white" alt="Buy Me a Coffee"></a>
|
|
67
|
+
</p>
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Overview
|
|
72
|
+
|
|
73
|
+
**perceptimg** is a Python library for perceptual, policy-driven image optimization. Unlike traditional tools that optimize for file size or a single quality knob, perceptimg analyzes image content, interprets declarative policies, tests multiple strategies, and selects the best candidate based on perceptual quality metrics.
|
|
74
|
+
|
|
75
|
+
### Key Features
|
|
76
|
+
|
|
77
|
+
| Feature | Description |
|
|
78
|
+
|---------|-------------|
|
|
79
|
+
| **Policy-Driven** | Declarative constraints for size, quality, and use case |
|
|
80
|
+
| **Content-Aware** | Analyzes images for text, faces, and edge density |
|
|
81
|
+
| **Multi-Format** | JPEG, PNG, WebP, AVIF, JXL, HEIF, GIF, TIFF, APNG |
|
|
82
|
+
| **Perceptual Metrics** | SSIM, PSNR, and weighted perceptual scoring |
|
|
83
|
+
| **Explainable Results** | Full decision trail for every optimization |
|
|
84
|
+
| **Batch Processing** | Parallel processing with progress callbacks |
|
|
85
|
+
| **Enterprise Ready** | Checkpointing, retry, rate limiting, Prometheus metrics |
|
|
86
|
+
| **Clean Architecture** | Dependency inversion, modular engines, extensible |
|
|
87
|
+
|
|
88
|
+
### Supported Formats
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
Core Formats JPEG, PNG, TIFF, GIF (always available via Pillow)
|
|
92
|
+
Modern Formats WebP, AVIF, JPEG XL (JXL), HEIF/HEIC, APNG (codec-dependent)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Installation
|
|
98
|
+
|
|
99
|
+
### From PyPI (Recommended)
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
pip install perceptimg
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### From Source
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
git clone https://github.com/seifreed/perceptimg.git
|
|
109
|
+
cd perceptimg
|
|
110
|
+
python3 -m venv venv
|
|
111
|
+
source venv/bin/activate # Windows: venv\Scripts\activate
|
|
112
|
+
pip install -e ".[dev]"
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## Quick Start
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
from perceptimg import optimize, Policy
|
|
121
|
+
|
|
122
|
+
# Define policy constraints
|
|
123
|
+
policy = Policy(
|
|
124
|
+
max_size_kb=150,
|
|
125
|
+
min_ssim=0.97,
|
|
126
|
+
preserve_text=True,
|
|
127
|
+
target_use_case="web"
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
# Optimize image
|
|
131
|
+
result = optimize("input.png", policy)
|
|
132
|
+
|
|
133
|
+
# Access results
|
|
134
|
+
print(f"Format: {result.report.chosen_format}")
|
|
135
|
+
print(f"Size: {result.report.size_before_kb:.1f}KB → {result.report.size_after_kb:.1f}KB")
|
|
136
|
+
print(f"SSIM: {result.report.ssim:.4f}")
|
|
137
|
+
print(f"Reasons: {result.report.reasons}")
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Usage
|
|
143
|
+
|
|
144
|
+
### Command Line Interface
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
# Basic optimization
|
|
148
|
+
perceptimg optimize input.png --policy policy.json --out output.webp
|
|
149
|
+
|
|
150
|
+
# With inline constraints
|
|
151
|
+
perceptimg optimize input.png --max-size-kb 100 --min-ssim 0.95 --formats webp,avif
|
|
152
|
+
|
|
153
|
+
# JSON output
|
|
154
|
+
perceptimg optimize input.png --policy policy.json --log-json
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Available Options
|
|
158
|
+
|
|
159
|
+
| Option | Description |
|
|
160
|
+
|--------|-------------|
|
|
161
|
+
| `--policy` | Path to JSON policy file |
|
|
162
|
+
| `--out` | Output file path |
|
|
163
|
+
| `--max-size-kb` | Maximum output size in KB |
|
|
164
|
+
| `--min-ssim` | Minimum SSIM threshold (0.0-1.0) |
|
|
165
|
+
| `--formats` | Preferred formats (comma-separated) |
|
|
166
|
+
| `--preserve-text` | Prefer text clarity |
|
|
167
|
+
| `--preserve-faces` | Prefer face quality |
|
|
168
|
+
| `--log-json` | Structured JSON logging |
|
|
169
|
+
| `--log-level` | Log level (DEBUG, INFO, WARNING) |
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## Policy Configuration
|
|
174
|
+
|
|
175
|
+
### Python API
|
|
176
|
+
|
|
177
|
+
```python
|
|
178
|
+
from perceptimg import Policy
|
|
179
|
+
|
|
180
|
+
policy = Policy(
|
|
181
|
+
max_size_kb=120,
|
|
182
|
+
min_ssim=0.98,
|
|
183
|
+
preserve_text=True,
|
|
184
|
+
preserve_faces=True,
|
|
185
|
+
allow_lossy=True,
|
|
186
|
+
preferred_formats=("webp", "avif", "jpeg"),
|
|
187
|
+
target_use_case="mobile",
|
|
188
|
+
)
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### JSON Configuration
|
|
192
|
+
|
|
193
|
+
```json
|
|
194
|
+
{
|
|
195
|
+
"max_size_kb": 150,
|
|
196
|
+
"min_ssim": 0.97,
|
|
197
|
+
"preserve_text": true,
|
|
198
|
+
"preserve_faces": false,
|
|
199
|
+
"allow_lossy": true,
|
|
200
|
+
"preferred_formats": ["webp", "avif", "jpeg"],
|
|
201
|
+
"target_use_case": "web"
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## Batch Processing
|
|
208
|
+
|
|
209
|
+
### Basic Batch
|
|
210
|
+
|
|
211
|
+
```python
|
|
212
|
+
from perceptimg import Policy, optimize_batch
|
|
213
|
+
|
|
214
|
+
policy = Policy(max_size_kb=100, min_ssim=0.9)
|
|
215
|
+
images = ["img1.png", "img2.png", "img3.png"]
|
|
216
|
+
|
|
217
|
+
result = optimize_batch(images, policy)
|
|
218
|
+
print(f"Success: {len(result.successful)}/{result.total}")
|
|
219
|
+
print(f"Success rate: {result.success_rate:.1%}")
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### Async Processing
|
|
223
|
+
|
|
224
|
+
```python
|
|
225
|
+
import asyncio
|
|
226
|
+
from perceptimg import Policy, optimize_batch_async
|
|
227
|
+
|
|
228
|
+
async def main():
|
|
229
|
+
policy = Policy(max_size_kb=100)
|
|
230
|
+
result = await optimize_batch_async(images, policy)
|
|
231
|
+
print(f"Processed {result.total} images")
|
|
232
|
+
|
|
233
|
+
asyncio.run(main())
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Memory-Efficient Streaming
|
|
237
|
+
|
|
238
|
+
```python
|
|
239
|
+
from perceptimg import Policy, optimize_lazy
|
|
240
|
+
|
|
241
|
+
policy = Policy(max_size_kb=100)
|
|
242
|
+
|
|
243
|
+
for path, result in optimize_lazy(large_image_list, policy):
|
|
244
|
+
if isinstance(result, Exception):
|
|
245
|
+
print(f"Error {path}: {result}")
|
|
246
|
+
else:
|
|
247
|
+
print(f"OK {path}: {result.report.size_after_kb:.1f}KB")
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## Enterprise Features
|
|
253
|
+
|
|
254
|
+
### Checkpoint/Resume
|
|
255
|
+
|
|
256
|
+
```python
|
|
257
|
+
from perceptimg import Policy, optimize_batch_with_checkpoint
|
|
258
|
+
|
|
259
|
+
result = optimize_batch_with_checkpoint(
|
|
260
|
+
images,
|
|
261
|
+
policy,
|
|
262
|
+
checkpoint_path="checkpoint.json",
|
|
263
|
+
checkpoint_interval=10, # Save every 10 images
|
|
264
|
+
)
|
|
265
|
+
# Resume after interruption by calling again with same checkpoint_path
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### Retry with Exponential Backoff
|
|
269
|
+
|
|
270
|
+
```python
|
|
271
|
+
from perceptimg import Policy, optimize_batch_with_retry
|
|
272
|
+
from perceptimg.core.retry import RetryConfig
|
|
273
|
+
|
|
274
|
+
retry_config = RetryConfig(max_retries=3, base_delay_ms=100)
|
|
275
|
+
|
|
276
|
+
result = optimize_batch_with_retry(
|
|
277
|
+
images,
|
|
278
|
+
policy,
|
|
279
|
+
retry_config=retry_config,
|
|
280
|
+
continue_on_error=True,
|
|
281
|
+
)
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### Rate Limiting
|
|
285
|
+
|
|
286
|
+
```python
|
|
287
|
+
from perceptimg import Policy, optimize_batch_with_rate_limit
|
|
288
|
+
from perceptimg.core.rate_limiter import RateLimitConfig
|
|
289
|
+
|
|
290
|
+
rate_limit = RateLimitConfig(requests_per_second=5)
|
|
291
|
+
|
|
292
|
+
result = optimize_batch_with_rate_limit(
|
|
293
|
+
images,
|
|
294
|
+
policy,
|
|
295
|
+
rate_limit=rate_limit,
|
|
296
|
+
)
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### Prometheus Metrics
|
|
300
|
+
|
|
301
|
+
```python
|
|
302
|
+
from perceptimg import Policy, optimize_batch_with_metrics
|
|
303
|
+
from perceptimg.core.metrics_exporter import MetricsCollector
|
|
304
|
+
|
|
305
|
+
metrics = MetricsCollector()
|
|
306
|
+
result, stats = optimize_batch_with_metrics(images, policy, metrics=metrics)
|
|
307
|
+
|
|
308
|
+
print(f"Average SSIM: {stats['average_ssim']:.2f}")
|
|
309
|
+
print(f"Compression ratio: {stats['average_compression_ratio']:.1%}")
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
|
|
314
|
+
## Architecture
|
|
315
|
+
|
|
316
|
+
```
|
|
317
|
+
perceptimg/
|
|
318
|
+
├── adapters/ # Framework adapters (PIL)
|
|
319
|
+
│ └── pil_adapter.py
|
|
320
|
+
├── core/ # Domain logic
|
|
321
|
+
│ ├── interfaces.py # Abstractions (ImageAdapter Protocol)
|
|
322
|
+
│ ├── analyzer.py # Content analysis
|
|
323
|
+
│ ├── metrics.py # SSIM, PSNR, perceptual scoring
|
|
324
|
+
│ ├── optimizer.py # Orchestration
|
|
325
|
+
│ ├── policy.py # Declarative constraints
|
|
326
|
+
│ ├── strategy.py # Candidate generation
|
|
327
|
+
│ └── batch/ # Batch processing
|
|
328
|
+
├── engines/ # Format-specific encoders
|
|
329
|
+
│ ├── webp_engine.py
|
|
330
|
+
│ ├── avif_engine.py
|
|
331
|
+
│ └── ...
|
|
332
|
+
└── utils/ # IO, heuristics, logging
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
### Clean Architecture Compliance
|
|
336
|
+
|
|
337
|
+
| Layer | Responsibility |
|
|
338
|
+
|-------|----------------|
|
|
339
|
+
| **Core** | Business logic, domain models, policies |
|
|
340
|
+
| **Adapters** | Framework-specific implementations |
|
|
341
|
+
| **Engines** | Format-specific encoding |
|
|
342
|
+
| **Utils** | Infrastructure services |
|
|
343
|
+
|
|
344
|
+
---
|
|
345
|
+
|
|
346
|
+
## Extensibility
|
|
347
|
+
|
|
348
|
+
### Custom Optimization Engine
|
|
349
|
+
|
|
350
|
+
```python
|
|
351
|
+
from perceptimg.engines.base import OptimizationEngine, EngineResult
|
|
352
|
+
from perceptimg.core.optimizer import Optimizer, register_engine
|
|
353
|
+
|
|
354
|
+
class MyCustomEngine(OptimizationEngine):
|
|
355
|
+
format = "custom"
|
|
356
|
+
priority = 100
|
|
357
|
+
|
|
358
|
+
@property
|
|
359
|
+
def is_available(self) -> bool:
|
|
360
|
+
return True
|
|
361
|
+
|
|
362
|
+
def optimize(self, image, strategy) -> EngineResult:
|
|
363
|
+
# Custom optimization logic
|
|
364
|
+
return EngineResult(data=optimized_bytes, format="custom", quality=90)
|
|
365
|
+
|
|
366
|
+
# Register custom engine
|
|
367
|
+
optimizer = Optimizer()
|
|
368
|
+
register_engine(optimizer, MyCustomEngine())
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
### Custom Analyzer
|
|
372
|
+
|
|
373
|
+
```python
|
|
374
|
+
from perceptimg.core.analyzer import Analyzer
|
|
375
|
+
|
|
376
|
+
class CustomAnalyzer(Analyzer):
|
|
377
|
+
def analyze(self, image):
|
|
378
|
+
result = super().analyze(image)
|
|
379
|
+
# Add custom heuristics
|
|
380
|
+
result.custom_score = self._compute_custom(image)
|
|
381
|
+
return result
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
---
|
|
385
|
+
|
|
386
|
+
## API Reference
|
|
387
|
+
|
|
388
|
+
### OptimizationResult
|
|
389
|
+
|
|
390
|
+
| Field | Type | Description |
|
|
391
|
+
|-------|------|-------------|
|
|
392
|
+
| `image_bytes` | `bytes` | Optimized image data |
|
|
393
|
+
| `image` | `PIL.Image` | Pillow image object |
|
|
394
|
+
| `report` | `OptimizationReport` | Detailed results |
|
|
395
|
+
|
|
396
|
+
### OptimizationReport
|
|
397
|
+
|
|
398
|
+
| Field | Type | Description |
|
|
399
|
+
|-------|------|-------------|
|
|
400
|
+
| `chosen_format` | `str` | Selected format |
|
|
401
|
+
| `quality` | `int` | Quality setting |
|
|
402
|
+
| `size_before_kb` | `float` | Original size |
|
|
403
|
+
| `size_after_kb` | `float` | Optimized size |
|
|
404
|
+
| `ssim` | `float` | SSIM score |
|
|
405
|
+
| `psnr` | `float` | PSNR score |
|
|
406
|
+
| `perceptual_score` | `float` | Weighted quality score |
|
|
407
|
+
| `reasons` | `list[str]` | Decision trail |
|
|
408
|
+
|
|
409
|
+
---
|
|
410
|
+
|
|
411
|
+
## Requirements
|
|
412
|
+
|
|
413
|
+
- Python 3.13+
|
|
414
|
+
- Pillow (PIL fork)
|
|
415
|
+
- NumPy
|
|
416
|
+
- scikit-image
|
|
417
|
+
- See [pyproject.toml](pyproject.toml) for full dependencies
|
|
418
|
+
|
|
419
|
+
---
|
|
420
|
+
|
|
421
|
+
## Quality
|
|
422
|
+
|
|
423
|
+
This project enforces:
|
|
424
|
+
|
|
425
|
+
| Tool | Purpose |
|
|
426
|
+
|------|---------|
|
|
427
|
+
| `ruff` | Linting |
|
|
428
|
+
| `black` | Formatting |
|
|
429
|
+
| `mypy` | Type checking |
|
|
430
|
+
| `bandit` | Security analysis |
|
|
431
|
+
| `pytest` | Testing (145 tests) |
|
|
432
|
+
|
|
433
|
+
```bash
|
|
434
|
+
# Run all quality checks
|
|
435
|
+
ruff check perceptimg tests
|
|
436
|
+
black perceptimg tests --check
|
|
437
|
+
mypy perceptimg --ignore-missing-imports
|
|
438
|
+
bandit -r perceptimg -q --exclude perceptimg/tests
|
|
439
|
+
pytest tests/
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
---
|
|
443
|
+
|
|
444
|
+
## Contributing
|
|
445
|
+
|
|
446
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
447
|
+
|
|
448
|
+
1. Fork the repository
|
|
449
|
+
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
|
450
|
+
3. Commit your changes (`git commit -m 'Add amazing feature'`)
|
|
451
|
+
4. Push to the branch (`git push origin feature/amazing-feature`)
|
|
452
|
+
5. Open a Pull Request
|
|
453
|
+
|
|
454
|
+
---
|
|
455
|
+
|
|
456
|
+
## Support the Project
|
|
457
|
+
|
|
458
|
+
If you find perceptimg useful, consider supporting its development:
|
|
459
|
+
|
|
460
|
+
<a href="https://buymeacoffee.com/seifreed" target="_blank">
|
|
461
|
+
<img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" height="50">
|
|
462
|
+
</a>
|
|
463
|
+
|
|
464
|
+
---
|
|
465
|
+
|
|
466
|
+
## License
|
|
467
|
+
|
|
468
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
469
|
+
|
|
470
|
+
**Attribution Required:**
|
|
471
|
+
- Author: **Marc Rivero** | [@seifreed](https://github.com/seifreed)
|
|
472
|
+
- Repository: [github.com/seifreed/perceptimg](https://github.com/seifreed/perceptimg)
|
|
473
|
+
|
|
474
|
+
---
|
|
475
|
+
|
|
476
|
+
<p align="center">
|
|
477
|
+
<sub>Made with dedication for the image optimization community</sub>
|
|
478
|
+
</p>
|