llm-imageops 1.0.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.
Files changed (33) hide show
  1. llm_imageops-1.0.0/PKG-INFO +421 -0
  2. llm_imageops-1.0.0/README.md +382 -0
  3. llm_imageops-1.0.0/imageops/__init__.py +98 -0
  4. llm_imageops-1.0.0/imageops/config.py +135 -0
  5. llm_imageops-1.0.0/imageops/core/__init__.py +11 -0
  6. llm_imageops-1.0.0/imageops/core/processor.py +179 -0
  7. llm_imageops-1.0.0/imageops/core/result.py +93 -0
  8. llm_imageops-1.0.0/imageops/exceptions.py +57 -0
  9. llm_imageops-1.0.0/imageops/operations/__init__.py +20 -0
  10. llm_imageops-1.0.0/imageops/operations/compress.py +126 -0
  11. llm_imageops-1.0.0/imageops/operations/encode.py +76 -0
  12. llm_imageops-1.0.0/imageops/operations/resize.py +227 -0
  13. llm_imageops-1.0.0/imageops/operations/slice.py +158 -0
  14. llm_imageops-1.0.0/imageops/providers/__init__.py +14 -0
  15. llm_imageops-1.0.0/imageops/providers/anthropic.py +53 -0
  16. llm_imageops-1.0.0/imageops/providers/base.py +34 -0
  17. llm_imageops-1.0.0/imageops/providers/registry.py +27 -0
  18. llm_imageops-1.0.0/imageops/strategies/__init__.py +9 -0
  19. llm_imageops-1.0.0/imageops/strategies/sequential.py +295 -0
  20. llm_imageops-1.0.0/imageops/utils/__init__.py +19 -0
  21. llm_imageops-1.0.0/imageops/utils/cleanup.py +122 -0
  22. llm_imageops-1.0.0/imageops/utils/download.py +116 -0
  23. llm_imageops-1.0.0/imageops/utils/logging.py +55 -0
  24. llm_imageops-1.0.0/imageops/utils/metadata.py +141 -0
  25. llm_imageops-1.0.0/imageops/utils/validation.py +119 -0
  26. llm_imageops-1.0.0/llm_imageops.egg-info/PKG-INFO +421 -0
  27. llm_imageops-1.0.0/llm_imageops.egg-info/SOURCES.txt +31 -0
  28. llm_imageops-1.0.0/llm_imageops.egg-info/dependency_links.txt +1 -0
  29. llm_imageops-1.0.0/llm_imageops.egg-info/requires.txt +15 -0
  30. llm_imageops-1.0.0/llm_imageops.egg-info/top_level.txt +1 -0
  31. llm_imageops-1.0.0/pyproject.toml +74 -0
  32. llm_imageops-1.0.0/setup.cfg +4 -0
  33. llm_imageops-1.0.0/setup.py +18 -0
@@ -0,0 +1,421 @@
1
+ Metadata-Version: 2.4
2
+ Name: llm-imageops
3
+ Version: 1.0.0
4
+ Summary: Production-grade async image preprocessing for LLM APIs
5
+ Author: ImageOps Contributors
6
+ License: MIT
7
+ Project-URL: Homepage, https://gitlab.com/yourusername/imageops
8
+ Project-URL: Repository, https://gitlab.com/yourusername/imageops
9
+ Project-URL: Issues, https://gitlab.com/yourusername/imageops/-/issues
10
+ Keywords: llm,image-processing,anthropic,async,ai,agents,computer-vision
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Framework :: AsyncIO
20
+ Classifier: Topic :: Software Development :: Libraries
21
+ Classifier: Topic :: Scientific/Engineering :: Image Processing
22
+ Requires-Python: >=3.9
23
+ Description-Content-Type: text/markdown
24
+ Requires-Dist: httpx>=0.28.0
25
+ Requires-Dist: aiofiles>=25.0.0
26
+ Requires-Dist: pydantic>=2.12.0
27
+ Requires-Dist: pydantic-settings>=2.12.0
28
+ Requires-Dist: opencv-python-headless>=4.12.0
29
+ Requires-Dist: pillow>=12.0.0
30
+ Requires-Dist: numpy>=2.2.0
31
+ Provides-Extra: dev
32
+ Requires-Dist: pytest>=7.4.0; extra == "dev"
33
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
34
+ Requires-Dist: pytest-cov>=4.1.0; extra == "dev"
35
+ Requires-Dist: black>=23.0.0; extra == "dev"
36
+ Requires-Dist: ruff>=0.1.0; extra == "dev"
37
+ Requires-Dist: mypy>=1.5.0; extra == "dev"
38
+ Dynamic: requires-python
39
+
40
+ # ImageOps: Production-Grade Image Preprocessing for LLMs
41
+
42
+ **Simple, fast, and production-ready async image preprocessing for LLM APIs.**
43
+
44
+ > **PyPI Package**: `llm-imageops` (import as `imageops`)
45
+
46
+ ImageOps handles all the complexity of preparing images for LLM providers like Anthropic Claude, automatically handling resizing, compression, slicing, and encoding based on each provider's limits.
47
+
48
+ ## ๐Ÿš€ Features
49
+
50
+ - **Zero Configuration** - Works out of the box with sensible defaults
51
+ - **100% Async** - Built for modern async Python applications
52
+ - **Production Ready** - Comprehensive error handling, security, resource limits
53
+ - **Provider Agnostic** - Easy to add support for new LLM providers
54
+ - **Type Safe** - Full type hints with Pydantic validation
55
+ - **Edge Case Handling** - Animated GIFs, panoramas, EXIF orientation, alpha channels
56
+ - **Smart Optimization** - Only processes when necessary based on provider limits
57
+ - **Resource Safe** - Automatic cleanup with async context managers
58
+
59
+ ## ๐Ÿ“ฆ Installation
60
+
61
+ ```bash
62
+ pip install llm-imageops
63
+ ```
64
+
65
+ Import as:
66
+ ```python
67
+ import imageops # Module name stays 'imageops'
68
+ ```
69
+
70
+ Or install from source:
71
+
72
+ ```bash
73
+ git clone https://github.com/yourusername/imageops.git
74
+ cd imageops
75
+ pip install -e .
76
+ ```
77
+
78
+ ## ๐ŸŽฏ Quick Start
79
+
80
+ ### Simple One-Liner
81
+
82
+ ```python
83
+ import imageops
84
+ import asyncio
85
+
86
+ async def main():
87
+ # Process image for Anthropic Claude
88
+ result = await imageops.process("image.jpg", provider="anthropic")
89
+
90
+ # Use in your LLM API call
91
+ messages = [
92
+ {
93
+ "role": "user",
94
+ "content": result.to_anthropic_format() + [
95
+ {"type": "text", "text": "What's in this image?"}
96
+ ]
97
+ }
98
+ ]
99
+
100
+ asyncio.run(main())
101
+ ```
102
+
103
+ ### Process from URL
104
+
105
+ ```python
106
+ result = await imageops.process(
107
+ "https://example.com/image.jpg",
108
+ provider="anthropic"
109
+ )
110
+ ```
111
+
112
+ ### Get File Paths Instead of Base64
113
+
114
+ ```python
115
+ result = await imageops.process(
116
+ "large_image.jpg",
117
+ provider="anthropic",
118
+ output_format="file" # Returns processed file paths
119
+ )
120
+ ```
121
+
122
+ ### With Progress Callback
123
+
124
+ ```python
125
+ async def progress_callback(step: str, progress: float):
126
+ print(f"{step}: {progress:.1%}")
127
+
128
+ result = await imageops.process(
129
+ "huge_image.jpg",
130
+ provider="anthropic",
131
+ progress_callback=progress_callback
132
+ )
133
+ ```
134
+
135
+ ### Batch Processing
136
+
137
+ ```python
138
+ results = await imageops.process_batch(
139
+ ["img1.jpg", "img2.jpg", "img3.jpg"],
140
+ provider="anthropic"
141
+ )
142
+
143
+ for result in results:
144
+ print(f"Processed {result.slice_count} image(s)")
145
+ ```
146
+
147
+ ## ๐Ÿ”ง Advanced Usage
148
+
149
+ ### Custom Configuration
150
+
151
+ ```python
152
+ import imageops
153
+
154
+ config = imageops.ImageOpsConfig(
155
+ max_image_size_mb=50, # Reject images > 50MB
156
+ max_batch_size=20, # Limit batch processing
157
+ enable_logging=True,
158
+ log_level="DEBUG",
159
+ default_quality=85 # JPEG quality
160
+ )
161
+
162
+ async with imageops.ImageProcessor(provider="anthropic", config=config) as processor:
163
+ result1 = await processor.process("img1.jpg")
164
+ result2 = await processor.process("img2.jpg", timeout=60.0)
165
+ # Automatic cleanup
166
+ ```
167
+
168
+ ### Custom Provider
169
+
170
+ ```python
171
+ from imageops.providers import BaseProvider, ProviderConfig, register_provider
172
+
173
+ class MyCustomProvider(BaseProvider):
174
+ name = "my_llm"
175
+
176
+ config = ProviderConfig(
177
+ max_width=4096,
178
+ max_height=4096,
179
+ max_size_mb=5.0,
180
+ max_base64_size_mb=10.0,
181
+ max_images_per_call=10,
182
+ supported_formats=["jpeg", "png"]
183
+ )
184
+
185
+ def needs_processing(self, metadata) -> bool:
186
+ return metadata.width > 4096 or metadata.size_mb > 5.0
187
+
188
+ def format_for_api(self, images):
189
+ # Return your provider's format
190
+ pass
191
+
192
+ # Register it
193
+ register_provider("my_llm", MyCustomProvider())
194
+
195
+ # Use it
196
+ result = await imageops.process("image.jpg", provider="my_llm")
197
+ ```
198
+
199
+ ## ๐Ÿ“Š What It Does
200
+
201
+ ImageOps automatically handles:
202
+
203
+ ### Resizing
204
+ - Reduces width/height to fit provider limits
205
+ - Maintains aspect ratio
206
+ - Uses high-quality INTER_AREA interpolation
207
+
208
+ ### Slicing
209
+ - Splits very tall images into vertical segments
210
+ - Splits very wide panoramas into horizontal slices
211
+ - Processes each segment independently
212
+
213
+ ### Compression
214
+ - Iterative quality reduction (95 โ†’ 10)
215
+ - Falls back to dimension reduction if needed
216
+ - Ensures file size stays under limits
217
+
218
+ ### Edge Cases
219
+ - **Animated GIFs** - Extracts first frame
220
+ - **EXIF Orientation** - Auto-rotates images
221
+ - **Alpha Channels** - Converts RGBA โ†’ RGB on white background
222
+ - **Wide Panoramas** - Horizontal slicing for 50000x1000 images
223
+ - **Corrupted Images** - Clear error messages
224
+
225
+ ## ๐Ÿ”’ Security
226
+
227
+ - **Path Traversal Protection** - Validates file paths
228
+ - **File Size Limits** - Rejects images > 100MB by default
229
+ - **Dimension Limits** - Maximum 100,000px per side
230
+ - **Format Whitelist** - Only allows safe formats
231
+ - **URL Validation** - Proper URL format checking
232
+
233
+ ## ๐Ÿ“ˆ Result Format
234
+
235
+ ```python
236
+ result = await imageops.process("image.jpg")
237
+
238
+ # Result attributes
239
+ result.success # bool
240
+ result.images # List[ImageOutput]
241
+ result.output_format # "base64" or "file"
242
+ result.was_sliced # bool
243
+ result.slice_count # int
244
+ result.processing_time_ms # float
245
+ result.provider # str
246
+ result.original_metadata # Dict
247
+
248
+ # Each image has THREE ways to access base64 + media type:
249
+
250
+ # 1. Separate fields (most flexible)
251
+ for img in result.images:
252
+ base64_data = img.data # Just the base64 string
253
+ media_type = img.media_type # e.g., "image/jpeg"
254
+ # Combine as needed: f"data:{media_type};base64,{base64_data}"
255
+
256
+ # 2. Data URI with prefix (convenience method)
257
+ for img in result.images:
258
+ data_uri = img.to_data_uri() # "data:image/jpeg;base64,<data>"
259
+
260
+ # 3. Anthropic API format (ready-to-use)
261
+ content = result.to_anthropic_format()
262
+ # Returns Anthropic-formatted content blocks with media_type included
263
+
264
+ # Other attributes
265
+ for img in result.images:
266
+ img.format # "base64" or "file"
267
+ img.width # int
268
+ img.height # int
269
+ img.size_bytes # int
270
+ img.was_compressed # bool
271
+ img.compression_quality # Optional[int]
272
+
273
+ # Convenience methods
274
+ result.to_anthropic_format() # Ready for Anthropic API
275
+ await result.cleanup() # Manual cleanup if needed
276
+ ```
277
+
278
+ ## โš™๏ธ Configuration Options
279
+
280
+ ```python
281
+ ImageOpsConfig(
282
+ # Resource Limits
283
+ max_image_size_mb=100.0, # Max input image size
284
+ max_dimension=100000, # Max width/height
285
+ max_batch_size=50, # Max images per batch
286
+
287
+ # Performance
288
+ max_concurrent_ops=5, # Concurrent operations
289
+ download_timeout=10.0, # Download timeout (seconds)
290
+ processing_timeout=300.0, # Processing timeout (seconds)
291
+
292
+ # Retry Logic
293
+ max_download_retries=3, # Download retry attempts
294
+ retry_backoff_factor=2.0, # Exponential backoff
295
+
296
+ # Compression
297
+ default_quality=90, # JPEG quality (1-100)
298
+ quality_step=5, # Quality reduction step
299
+ min_quality=10, # Minimum quality
300
+
301
+ # Logging
302
+ enable_logging=True,
303
+ log_level="INFO", # DEBUG, INFO, WARNING, ERROR
304
+ log_file=None, # Optional log file
305
+
306
+ # Security
307
+ validate_paths=True, # Path traversal protection
308
+ allowed_formats=[".jpg", ".jpeg", ".png", ".gif", ".webp"]
309
+ )
310
+ ```
311
+
312
+ ## ๐Ÿงช Testing
313
+
314
+ The package includes comprehensive tests:
315
+
316
+ ```bash
317
+ # Install dev dependencies
318
+ pip install -e ".[dev]"
319
+
320
+ # Run all tests
321
+ pytest
322
+
323
+ # Run with coverage
324
+ pytest --cov=imageops --cov-report=html
325
+
326
+ # Run specific test categories
327
+ pytest tests/test_operations.py
328
+ pytest tests/test_edge_cases.py
329
+ ```
330
+
331
+ ## ๐Ÿ› Troubleshooting
332
+
333
+ ### Image Download Fails
334
+
335
+ ```python
336
+ from imageops.exceptions import ImageDownloadError
337
+
338
+ try:
339
+ result = await imageops.process("https://example.com/image.jpg")
340
+ except ImageDownloadError as e:
341
+ print(f"Download failed: {e}")
342
+ # Handle fallback
343
+ ```
344
+
345
+ ### Image Too Large
346
+
347
+ ```python
348
+ from imageops.exceptions import ImageTooLargeError
349
+
350
+ try:
351
+ result = await imageops.process("huge.jpg")
352
+ except ImageTooLargeError as e:
353
+ print(f"Image exceeds limit: {e}")
354
+ ```
355
+
356
+ ### Processing Timeout
357
+
358
+ ```python
359
+ from imageops.exceptions import TimeoutError
360
+
361
+ try:
362
+ result = await imageops.process("image.jpg", timeout=30.0)
363
+ except TimeoutError as e:
364
+ print(f"Processing took too long: {e}")
365
+ ```
366
+
367
+ ### Enable Debug Logging
368
+
369
+ ```python
370
+ config = imageops.ImageOpsConfig(
371
+ enable_logging=True,
372
+ log_level="DEBUG",
373
+ log_file="imageops.log"
374
+ )
375
+
376
+ processor = imageops.ImageProcessor(config=config)
377
+ ```
378
+
379
+ ## ๐Ÿ—๏ธ Architecture
380
+
381
+ ```
382
+ imageops/
383
+ โ”œโ”€โ”€ core/ # Main processor and result types
384
+ โ”œโ”€โ”€ providers/ # Provider implementations
385
+ โ”œโ”€โ”€ operations/ # Image operations (resize, slice, compress, encode)
386
+ โ”œโ”€โ”€ strategies/ # Processing strategies
387
+ โ”œโ”€โ”€ utils/ # Utilities (validation, cleanup, logging, metadata)
388
+ โ”œโ”€โ”€ config.py # Configuration
389
+ โ””โ”€โ”€ exceptions.py # Exception hierarchy
390
+ ```
391
+
392
+ ## ๐Ÿค Contributing
393
+
394
+ Contributions are welcome! Please:
395
+
396
+ 1. Fork the repository
397
+ 2. Create a feature branch (`git checkout -b feature/amazing-feature`)
398
+ 3. Commit your changes (`git commit -m 'Add amazing feature'`)
399
+ 4. Push to the branch (`git push origin feature/amazing-feature`)
400
+ 5. Open a Pull Request
401
+
402
+ ## ๐Ÿ“ License
403
+
404
+ MIT License - see LICENSE file for details.
405
+
406
+ ## ๐Ÿ™ Acknowledgments
407
+
408
+ Built with best practices from:
409
+ - [httpx](https://www.python-httpx.org/) - Async HTTP patterns
410
+ - [aiofiles](https://github.com/Tinche/aiofiles) - Async file I/O
411
+ - [pydantic](https://pydantic-docs.helpmanual.io/) - Type validation
412
+ - [opencv-python](https://github.com/opencv/opencv-python) - Image processing
413
+
414
+ ## ๐Ÿ“ง Support
415
+
416
+ - **Issues**: [GitHub Issues](https://github.com/yourusername/imageops/issues)
417
+ - **Discussions**: [GitHub Discussions](https://github.com/yourusername/imageops/discussions)
418
+
419
+ ---
420
+
421
+ Made with โค๏ธ for the LLM community