DILSHAD737 commited on
Commit
cd922fb
·
verified ·
1 Parent(s): 71aee75

Upload 3 files

Browse files
Files changed (3) hide show
  1. utils/advanced.py +91 -0
  2. utils/ai_analyzer.py +80 -0
  3. utils/compressor.py +132 -0
utils/advanced.py ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import hashlib
2
+ import numpy as np
3
+ from PIL import Image
4
+ import imagehash
5
+ from skimage.metrics import structural_similarity as ssim
6
+ import cv2
7
+
8
+ class AdvancedFeatures:
9
+ @staticmethod
10
+ def perceptual_hash(image_path):
11
+ """Generate perceptual hash for deduplication"""
12
+ img = Image.open(image_path)
13
+ return imagehash.phash(img, hash_size=16)
14
+
15
+ @staticmethod
16
+ def find_similar_images(image_paths, threshold=5):
17
+ """Group similar images by perceptual hash"""
18
+ hashes = {}
19
+ for path in image_paths:
20
+ try:
21
+ hashes[path] = AdvancedFeatures.perceptual_hash(path)
22
+ except:
23
+ pass
24
+
25
+ # Group images
26
+ groups = []
27
+ processed = set()
28
+
29
+ for path1, hash1 in hashes.items():
30
+ if path1 in processed:
31
+ continue
32
+
33
+ group = [path1]
34
+ for path2, hash2 in hashes.items():
35
+ if path2 != path1 and path2 not in processed:
36
+ if hash1 - hash2 <= threshold:
37
+ group.append(path2)
38
+ processed.add(path2)
39
+
40
+ groups.append(group)
41
+ processed.add(path1)
42
+
43
+ return groups
44
+
45
+ @staticmethod
46
+ def calculate_ssim(original_path, compressed_path):
47
+ """Calculate structural similarity index"""
48
+ img1 = cv2.imread(str(original_path))
49
+ img2 = cv2.imread(str(compressed_path))
50
+
51
+ # Resize to same dimensions
52
+ if img1.shape != img2.shape:
53
+ img2 = cv2.resize(img2, (img1.shape[1], img1.shape[0]))
54
+
55
+ # Convert to grayscale for SSIM
56
+ gray1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
57
+ gray2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)
58
+
59
+ score = ssim(gray1, gray2)
60
+ return score
61
+
62
+ @staticmethod
63
+ def recursive_optimize(compressor, img, target_size_kb, max_iterations=10):
64
+ """Find optimal quality to hit target file size"""
65
+ low, high = 30, 100
66
+ best_result = None
67
+ best_size_diff = float('inf')
68
+
69
+ for _ in range(max_iterations):
70
+ mid = (low + high) // 2
71
+
72
+ # Test at quality 'mid'
73
+ test_output = io.BytesIO()
74
+ img.save(test_output, format='WEBP', quality=mid)
75
+ size_kb = len(test_output.getvalue()) / 1024
76
+
77
+ diff = abs(size_kb - target_size_kb)
78
+
79
+ if diff < best_size_diff:
80
+ best_size_diff = diff
81
+ best_result = (mid, test_output.getvalue())
82
+
83
+ if size_kb > target_size_kb:
84
+ high = mid - 1
85
+ else:
86
+ low = mid + 1
87
+
88
+ if diff < target_size_kb * 0.05: # Within 5%
89
+ break
90
+
91
+ return best_result
utils/ai_analyzer.py ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+ import numpy as np
3
+ import torch
4
+ import torch.nn as nn
5
+ from PIL import Image
6
+ import torchvision.transforms as transforms
7
+
8
+ class ContentAnalyzer:
9
+ def __init__(self):
10
+ # Use lightweight model (MobileNetV2) - works on CPU
11
+ self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
12
+ self.model = self._load_model()
13
+
14
+ def _load_model(self):
15
+ # Load pre-trained MobileNetV2 for feature extraction
16
+ model = torch.hub.load('pytorch/vision:v0.10.0', 'mobilenet_v2', pretrained=True)
17
+ # Remove classification head to get features
18
+ model.classifier = nn.Identity()
19
+ model.eval()
20
+ return model.to(self.device)
21
+
22
+ def analyze(self, image_path):
23
+ """Returns image type and optimal compression strategy"""
24
+ # Load image
25
+ img = cv2.imread(str(image_path))
26
+ if img is None:
27
+ return "photo", {"method": "avif", "quality": 75}
28
+
29
+ h, w = img.shape[:2]
30
+
31
+ # 1. Edge detection (for text/screenshots)
32
+ gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
33
+ edges = cv2.Canny(gray, 50, 150)
34
+ edge_ratio = np.sum(edges > 0) / edges.size
35
+
36
+ # 2. Color analysis
37
+ unique_colors = len(np.unique(img.reshape(-1, img.shape[2]), axis=0))
38
+
39
+ # 3. Texture analysis (variance of Laplacian)
40
+ laplacian_var = cv2.Laplacian(gray, cv2.CV_64F).var()
41
+
42
+ # 4. Check for transparency
43
+ has_transparency = False
44
+ try:
45
+ pil_img = Image.open(image_path)
46
+ has_transparency = pil_img.mode in ('RGBA', 'LA', 'P') and 'transparency' in pil_img.info
47
+ except:
48
+ pass
49
+
50
+ # Decision logic
51
+ if has_transparency:
52
+ return "graphic_with_transparency", {
53
+ "method": "png_optimized",
54
+ "colors": 256,
55
+ "lossless": True
56
+ }
57
+ elif edge_ratio > 0.15 and unique_colors < 5000:
58
+ return "screenshot_or_text", {
59
+ "method": "webp_lossless",
60
+ "quality": 90,
61
+ "preserve_text": True
62
+ }
63
+ elif unique_colors < 1000:
64
+ return "graphic", {
65
+ "method": "png_quantized",
66
+ "colors": min(256, unique_colors),
67
+ "dither": 0.5
68
+ }
69
+ elif laplacian_var < 100:
70
+ return "smooth_gradient", {
71
+ "method": "avif",
72
+ "quality": 80,
73
+ "avoid_banding": True
74
+ }
75
+ else:
76
+ return "photo", {
77
+ "method": "avif",
78
+ "quality": 75,
79
+ "chroma_subsampling": "4:2:0"
80
+ }
utils/compressor.py ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import io
2
+ import subprocess
3
+ from PIL import Image, ImageOps
4
+ import cv2
5
+ import numpy as np
6
+ from pathlib import Path
7
+ import piexif
8
+
9
+ class ImageCompressor:
10
+ def __init__(self):
11
+ self.supported_formats = {'.jpg', '.jpeg', '.png', '.webp', '.gif', '.bmp', '.tiff'}
12
+
13
+ def compress(self, input_path, output_path, method="auto", **kwargs):
14
+ """Main compression entry point"""
15
+ img = Image.open(input_path)
16
+ original_size = Path(input_path).stat().st_size
17
+
18
+ # Get original format
19
+ original_format = img.format
20
+
21
+ if method == "auto":
22
+ # Use AI analyzer to determine best method
23
+ from .ai_analyzer import ContentAnalyzer
24
+ analyzer = ContentAnalyzer()
25
+ _, strategy = analyzer.analyze(input_path)
26
+ method = strategy.get("method", "avif")
27
+ kwargs = {**kwargs, **strategy}
28
+
29
+ # Apply compression based on method
30
+ if method == "avif":
31
+ compressed = self._compress_avif(img, kwargs.get('quality', 75))
32
+ output_path = str(output_path).replace(Path(output_path).suffix, '.avif')
33
+ elif method == "webp_lossless":
34
+ compressed = self._compress_webp_lossless(img, kwargs.get('quality', 90))
35
+ output_path = str(output_path).replace(Path(output_path).suffix, '.webp')
36
+ elif method == "png_optimized":
37
+ compressed = self._compress_png_optimized(img, kwargs.get('colors', 256))
38
+ output_path = str(output_path).replace(Path(output_path).suffix, '.png')
39
+ elif method == "png_quantized":
40
+ compressed = self._compress_png_quantized(img, kwargs.get('colors', 128))
41
+ output_path = str(output_path).replace(Path(output_path).suffix, '.png')
42
+ elif method == "jpeg_high_quality":
43
+ compressed = self._compress_jpeg(img, kwargs.get('quality', 85))
44
+ output_path = str(output_path).replace(Path(output_path).suffix, '.jpg')
45
+ else:
46
+ # Default to WebP
47
+ compressed = self._compress_webp(img, kwargs.get('quality', 80))
48
+ output_path = str(output_path).replace(Path(output_path).suffix, '.webp')
49
+
50
+ # Save compressed image
51
+ with open(output_path, 'wb') as f:
52
+ f.write(compressed)
53
+
54
+ compressed_size = Path(output_path).stat().st_size
55
+ ratio = (1 - compressed_size / original_size) * 100
56
+
57
+ return output_path, ratio
58
+
59
+ def _compress_avif(self, img, quality=75):
60
+ """AVIF compression - best for photos"""
61
+ output = io.BytesIO()
62
+
63
+ # Convert RGBA to RGB if needed (AVIF doesn't support alpha well)
64
+ if img.mode == 'RGBA':
65
+ background = Image.new('RGB', img.size, (255, 255, 255))
66
+ background.paste(img, mask=img.split()[-1])
67
+ img = background
68
+
69
+ # Save as AVIF (requires pillow-avif-plugin)
70
+ img.save(output, format='AVIF', quality=quality, speed=4)
71
+ return output.getvalue()
72
+
73
+ def _compress_webp_lossless(self, img, quality=90):
74
+ """Lossless WebP for text/screenshots"""
75
+ output = io.BytesIO()
76
+ img.save(output, format='WEBP', lossless=True, quality=quality, method=6)
77
+ return output.getvalue()
78
+
79
+ def _compress_webp(self, img, quality=80):
80
+ """Standard WebP compression"""
81
+ output = io.BytesIO()
82
+ img.save(output, format='WEBP', quality=quality, method=4)
83
+ return output.getvalue()
84
+
85
+ def _compress_png_optimized(self, img, colors=256):
86
+ """PNG with color palette reduction"""
87
+ output = io.BytesIO()
88
+
89
+ # Convert to palette mode
90
+ if colors < 256:
91
+ img = img.quantize(colors=colors, method=Image.MEDIANCUT)
92
+ img.save(output, format='PNG', optimize=True)
93
+ else:
94
+ img.save(output, format='PNG', optimize=True)
95
+
96
+ return output.getvalue()
97
+
98
+ def _compress_png_quantized(self, img, colors=128):
99
+ """Heavy PNG quantization for graphics"""
100
+ output = io.BytesIO()
101
+
102
+ # Reduce colors dramatically
103
+ img_quantized = img.quantize(colors=colors, method=Image.FASTOCTREE)
104
+ img_quantized.save(output, format='PNG', optimize=True)
105
+
106
+ return output.getvalue()
107
+
108
+ def _compress_jpeg(self, img, quality=85):
109
+ """JPEG compression"""
110
+ output = io.BytesIO()
111
+
112
+ # Convert to RGB if needed
113
+ if img.mode in ('RGBA', 'LA', 'P'):
114
+ img = img.convert('RGB')
115
+
116
+ img.save(output, format='JPEG', quality=quality, optimize=True, progressive=True)
117
+ return output.getvalue()
118
+
119
+ def smart_resize(self, img, target_dimension=2048):
120
+ """Intelligently resize if image is too large"""
121
+ width, height = img.size
122
+ max_dim = max(width, height)
123
+
124
+ if max_dim > target_dimension:
125
+ scale = target_dimension / max_dim
126
+ new_size = (int(width * scale), int(height * scale))
127
+ return img.resize(new_size, Image.Resampling.LANCZOS)
128
+ return img
129
+
130
+ def remove_metadata(self, image_path):
131
+ """Strip all EXIF metadata"""
132
+ piexif.remove(str(image_path))